Merge "Merging from ub-launcher3-master @ build 6327709" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-03-25 20:45:16 +00:00
committed by Android (Google) Code Review
94 changed files with 3089 additions and 2619 deletions
-22
View File
@@ -1,22 +0,0 @@
150522230
150260456
139137636
150122946
150260456
151166786
139828243
150876921
150644839
146593239
143361609
151025334
150864182
151050221
150680980
138964382
150788630
146593239
149792636
147305863
148867106
139137636
-19
View File
@@ -1,19 +0,0 @@
138964382
139137636
139828243
143361609
146593239
147305863
148867106
149792636
150122946
150260456
150522230
150644839
150680980
150788630
150864182
150876921
151025334
151050221
151166786
-48
View File
@@ -1,48 +0,0 @@
133381284 erosky P1 ASSIGNED [R] Move split-screen presentation/layout to system-ui. ----
139750033 sunnygoyal P2 ASSIGNED Overview misfired ----
139828243 xuqiu P2 ASSIGNED [Overview Actions] Add Overview actions ----
142753423 sfufa P3 ACCEPTED Prototype predictive hotseat ----
144052839 tracyzhou P2 ACCEPTED Improving Launcher preview in ThemePicker ----
144854916 winsonc P2 ASSIGNED Add winscope logging for systemui/launcher ----
145253300 awickham P1 FIXED [a11y]Talkback doesn't focus on pop up window when long press Smart space. ----
145297320 zakcohen P2 FIXED Overview layout with anchor chips ----
145595763 sfufa P4 FIXED Wrong Tab is highlighted after creating Work profile ----
145647019 twickham P1 FIXED [a11y] Use Talkback page navigation to switch apps in Overview, Talkback couldn't move focus across apps one by one. ----
148099851 peskal P1 FIXED Make reduced scale snapshots toggle on/off base on config_reducedTaskSnapshotScale=0 ----
148542211 awickham P2 ASSIGNED Sandbox for gesture nav tutorial ----
148867106 vadimt P2 ASSIGNED [Flaky test] AddConfigWidgetTest.testConfigCancelled ----
148896221 hyunyoungs P3 ASSIGNED Migrate to soong ----
148900990 hyunyoungs P2 FIXED Hard to know if there's a text field for naming folders without a cursor ----
149197172 sfufa P1 FIXED [Regression] New work profile OOBE doesn't adopt theme color ----
149198955 sfufa P2 FIXED Work tab flash seems superfluous with new OOBE ----
149199058 sfufa P1 FIXED Work profile OOBE not dismissed after locking/unlocking screen ----
149200572 sfufa P1 FIXED [Regression] Work profile toggle is obscured by system navigation bar ----
149215103 sfufa P1 FIXED OOBE still displayed after removing work profile ----
149422395 vadimt P2 ASSIGNED Test should fail with a clear diags if it can't install a required package ----
149481723 sfufa P2 FIXED Launcher OOBE for work profile and COPE devices improvements ----
149867607 sfufa P0 FIXED [Failing test] WorkTabTest.workTabExists ----
149870691 twickham P2 ASSIGNED Cleanup AppWindowAnimationHelper and TransformParams ----
149927292 sfufa P0 FIXED [Flaky test] Apps view did not bind WorkTabTest.toggleWorks ----
149935239 hyunyoungs P1 FIXED Update the suggestFolderName when items are added and deleted from folders ----
149967272 hyunyoungs P1 FIXED Tap on Edit Name interaction drops the first recommendation ----
149969889 sfufa P2 FIXED [Crash] java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.ComponentName ----
149993849 thiruram P1 FIXED Pixel launcher crashes when tapping on empty shortcut folder name ----
138964382 awickham P2 ASSIGNED Move all shared libs to a common location (plugin / iconloaderlib) ----
139137636 vadimt P2 ASSIGNED Create memory tests for Launcher ----
139828243 xuqiu P2 ASSIGNED [Overview Actions] Add Overview actions ----
143361609 twickham P2 ASSIGNED Overview gesture in R ----
146593239 awickham P2 FIXED Gesture navigation fail in Facebook messenger ----
147305863 sfufa P2 FIXED DragNDrop userevent missing target hierarchy ----
148867106 vadimt P2 FIXED [Flaky test] AddConfigWidgetTest.testConfigCancelled ----
149792636 dupin P2 ASSIGNED [Android 11] Blur polish ----
150122946 sfufa P4 FIXED Work toggle styling not correct ----
150260456 peanutbutter P1 FIXED Add Fixed Rotation Transform to Home Settings ----
150522230 hyunyoungs P1 ASSIGNED Pixel launcher keeps crashing ('java.lang.String android.content.ComponentName.getPackageName()) ----
150644839 twickham P3 FIXED In landscape mode, long press on app shortcut UI issue observed. ----
150680980 jonmiranda P2 FIXED Slide up to home screen animation is not smooth ----
150788630 hyunyoungs P1 ASSIGNED Turn off FOLDER_NAME_SUGGEST flag on QQ builds earliest possible ----
150864182 twickham P4 FIXED Crash when dumping while user is locked ----
150876921 jonmiranda P2 FIXED Pages views seem to snap back to previous page ----
151025334 hyunyoungs P1 VERIFIED DeviceConfig doesn't update immediately when Pheonotype pushes ----
151050221 dupin P1 FIXED [NO LAST KMSG] [Short Uptime] [R]DUT will display the "Pixel is starting..." after rebooting and unlocking SIM pin code ----
151166786 sunnygoyal P2 FIXED Swipe handler and activity interface mismatch ----
-777
View File
@@ -1,777 +0,0 @@
COMMAND>> git log f3779f129f7326cb7acb57bf6aabd68aca5b6218..3aaf3967348ff55d2b8ac6d50e59ea01d9362af9(B
commit 3aaf3967348ff55d2b8ac6d50e59ea01d9362af9
Author: Vinit Nayak <peanutbutter@google.com>
Date: Fri Mar 13 13:57:09 2020 -0700
Remove sensor manager from RecentsView
This disables the rotation animation in
overview, but should hopefully fix tests.
fixes: 150260456
Change-Id: I121cad155672c2e325cc0f83ce209be0d3806b1c
commit 9caed38e34671e6b07b95bea1e971dee03b010ce
Merge: 6d8203ef4 f546e0599
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Fri Mar 13 16:41:17 2020 +0000
Merge "Null check every ComponentName call inside FolderNameProvider" into ub-launcher3-master
commit f546e0599e8127068e7d75ff787483453c781275
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Thu Mar 12 12:53:43 2020 -0700
Null check every ComponentName call inside FolderNameProvider
Bug: 150522230
Change-Id: I50007a3a781234797e16d830935a8b8585ac242b
commit 6d8203ef4495f9550999f483f7c9822316b29971
Merge: 3af717835 3388323bc
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Fri Mar 13 03:00:46 2020 +0000
Merge "Disable OrientationListener if vertical landscape not supported" into ub-launcher3-master
commit 3388323bc1b598dcee37d9522c14f968a1318f8e
Author: Vinit Nayak <peanutbutter@google.com>
Date: Thu Mar 12 17:18:51 2020 -0700
Disable OrientationListener if vertical landscape not supported
Even if multiple orientations are disabled but the flag
is on, we'll be listneing and setting different layouts.
Seeing in some tests that that callback was getting fired
for some reason.
Fixes: 150260456
Change-Id: I0a1c9f06cc4830d3dc8410a777d595851f1c35eb
commit 3af717835652536726e42d46d5875adf5ceb3cb7
Author: thiruram <thiruram@google.com>
Date: Thu Mar 12 16:16:24 2020 -0700
Fixes missing smart folder logging bug. Uses ProtoLite.toString method to log LauncherEvents.
Change-Id: I45dbf189e7bd47f8d4d7ba55180e59686bd6ecae
commit b6bc08ad5751e360a0e0407f7fc5cd708b7a28be
Merge: 6aa63d9f8 984c01cbc
Author: Tony Wickham <twickham@google.com>
Date: Thu Mar 12 21:48:42 2020 +0000
Merge "Invert playNonAtomicComponent() as onlyPlayAtomicComponent()" into ub-launcher3-master
commit 6aa63d9f8ec9a20c431da2f0cd05be610ed0d152
Merge: 4e82f5bc3 f0d96f83f
Author: Vadim Tryshev <vadimt@google.com>
Date: Thu Mar 12 21:01:30 2020 +0000
Merge "Fixing activity leak via accumulation of draw listeners" into ub-launcher3-master
commit f0d96f83f72e81ffc0f14db60c50c022839bb6e7
Author: vadimt <vadimt@google.com>
Date: Tue Mar 10 13:44:58 2020 -0700
Fixing activity leak via accumulation of draw listeners
Bug: 139137636
Change-Id: I0a2f0849f886acaac31746ac7c9724c765692e88
commit 4e82f5bc364f66d9a6ac72071db0d18e5a7fd956
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Thu Mar 12 12:29:33 2020 -0700
Fixing crash when swiping up using 3P Launcher
Change-Id: Ia181edc1a00136374b3f0d848beccf0c9acd7b5c
commit f85fcc792f74b3143670cef9e260ffb1b68a1f9f
Merge: b9ec9319c f5a4deb12
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Thu Mar 12 18:12:59 2020 +0000
Merge "Hide work apps when work profile is paused" into ub-launcher3-master
commit f5a4deb120731f116aea6b2161560f4e81502260
Author: Samuel Fufa <sfufa@google.com>
Date: Wed Mar 4 16:24:06 2020 -0800
Hide work apps when work profile is paused
- hide overlay icon in landscape mode
- don't show edu if user has already seen legacy work profile edu
- make sure personal tab is highlighted when work profile is reinstalled
- always go home after a work profile is added or removed
- add tests for work edu flow
Bug: 150122946
Test: Manual
Change-Id: I8f80ac763acf03ca31a534464f4ddfd84528d329
commit b9ec9319c5534cf6ebd8df2ee1144e1ebb477c33
Author: Vinit Nayak <peanutbutter@google.com>
Date: Wed Mar 4 12:05:28 2020 -0800
Add fixed_rotation_transform to home settings
This sets the feature flag on launcher side
and also updates the setting in Settings.Global
Launcher DOES NOT listen to the Settings.Global
change from adb anymore. This should take
preference over setting it from command line.
Also fix a related swipe to home animation bug
that happened w/ merge conflict.
Fixes: 150260456
Test: Set and unset, visually verified behavior.
Tested w/ autorotate on and off.
Checked Settings.Global value correctly updated
via "adb shell settings get global
fixed_rotation_transform"
TODO: Update tests to reflect this new
default-on fixed rotation behavior.
Change-Id: Id95f006eb1e92a59e24b05567298fd21b1409b13
commit 984c01cbcda8ab31c24c6a27118d9c1934c23795
Author: Tony Wickham <twickham@google.com>
Date: Fri Mar 6 15:56:46 2020 -0800
Invert playNonAtomicComponent() as onlyPlayAtomicComponent()
This avoids the double negative we use in a few places, so should be clearer.
Also added some comments to explain what the animComponents are used for.
Change-Id: Ibd25bd12efce6553b377bbd9c0651e4f4ac3e498
commit 31ff98e14491edda33a7ccd2be04795bdaad124c
Merge: 4acdb3bcd 9e19866ed
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Thu Mar 12 00:26:57 2020 +0000
Merge "Only allow horizontal spring if page will change." into ub-launcher3-master
commit 4acdb3bcd7f69e0cd42b7fae16176cbe15b551aa
Merge: 6a550f26a 003782f93
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Thu Mar 12 00:20:15 2020 +0000
Merge "Removing some properties out of AnimationBuilder" into ub-launcher3-master
commit 6a550f26a79564575cee5dfe15338c64ffe00d2b
Merge: 0abe81991 fa617d89c
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Thu Mar 12 00:15:44 2020 +0000
Merge "Using FallbackSwipeHandler in 2-button mode" into ub-launcher3-master
commit 003782f93c8b5096ebf6e64fbfa7e3483c11d685
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Fri Mar 6 14:52:17 2020 -0800
Removing some properties out of AnimationBuilder
AnimationBuilder and PendingAnimation have similar logic. This will
allow to unify the two classes
Change-Id: Id8c1d8a7020d132adbccdc6c80538ed6556cb75e
commit 0abe81991398556485d6605dbeb57b64e2414f92
Merge: 9c47ddd2a 21167f01d
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Wed Mar 11 23:27:05 2020 +0000
Merge "[Overview Actions] Hide other tasks for select mode UI." into ub-launcher3-master
commit fa617d89cece2c08c03fa7506700bed93d060cf7
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Wed Mar 11 16:20:25 2020 -0700
Using FallbackSwipeHandler in 2-button mode
Bug: 151166786
Change-Id: Ia86af76c1779bafa4690e733a7e6764973c8ae0d
commit 21167f01d4877d5927e4cd473cf6fcf61acc54d8
Author: Becky Qiu <xuqiu@google.com>
Date: Thu Mar 5 11:26:34 2020 -0800
[Overview Actions] Hide other tasks for select mode UI.
Test:local
Bug:139828243
Change-Id: Idc9c6a0e354b9df7f48f3ce93b560fdc4999fc3a
commit 9e19866ed86de5237ea02810e28fb56f3ab80616
Author: Jon Miranda <jonmiranda@google.com>
Date: Wed Mar 11 14:42:02 2020 -0700
Only allow horizontal spring if page will change.
Bug: 150876921
Change-Id: I88db4c28ec3f8213c583e8a0dcd1cce2b1fee322
commit 9c47ddd2a60aacd5663e040cf1f90d65896544a4
Merge: 8780065fd ddb08885f
Author: Tony Wickham <twickham@google.com>
Date: Wed Mar 11 21:10:04 2020 +0000
Merge "Try orienting popup the other way if offset pushes it out of bounds" into ub-launcher3-master
commit 8780065fdbe9e0b012f110d523447bcf4022a191
Merge: 25960bcd8 2768a2468
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Wed Mar 11 20:17:17 2020 +0000
[automerger skipped] Merge "[DO NOT MERGE] Adds fling gesture suppression to Launcher" into ub-launcher3-qt-qpr1-dev am: 631ed598ee -s ours am: 2768a24688 -s ours
am skip reason: subject contains skip directive
Change-Id: I36a95c05e2cef2bb615c67c30697c8a1b07b4c6f
commit 25960bcd88b97727c9892dfce0ad57761ef3a992
Merge: 9c40c83d7 f3b22ffee
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Wed Mar 11 20:17:10 2020 +0000
[automerger skipped] [DO NOT MERGE] Adds fling gesture suppression to Launcher am: f3b22ffee6 -s ours
am skip reason: subject contains skip directive
Change-Id: I38695bd05d74b8a526e61c2b55b2d2669e4e31a7
commit 2768a24688171fddd0ed4e8fa720d308736c2ca8
Merge: f3b22ffee 631ed598e
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Wed Mar 11 20:05:05 2020 +0000
[automerger skipped] Merge "[DO NOT MERGE] Adds fling gesture suppression to Launcher" into ub-launcher3-qt-qpr1-dev am: 631ed598ee -s ours
am skip reason: subject contains skip directive
Change-Id: I6a1906e1e7e302ca6886f3ecdeac5d374476020e
commit ddb08885f9d51db577aedc386cc3a90d054132b2
Author: Tony Wickham <twickham@google.com>
Date: Tue Mar 10 18:25:31 2020 -0700
Try orienting popup the other way if offset pushes it out of bounds
orientAboutObject() currently determines whether the popup should align
its left side with the icon or the right side. However, after
determining this, there is an offset to ensure the popup lines up with
the icon as expected, which might push it out of bounds. In that case,
we fallback to centering the popup. However, there might be plenty of
room on the other side, so we should just align the other direction
instead. Updated the logic to do that by first trying to align left
(in LTR) or right (in RTL), then trying again with the other alignment
if it doesn't fit after all x calculations are made.
Bug: 150644839
Change-Id: I219dae331bf790e461d91394ffe025d40ec54c9b
commit f3b22ffee691dca3e8a5cc3c0a1fb1d19ce8a5ad
Author: Govinda Wasserman <gwasserman@google.com>
Date: Thu Mar 5 16:50:22 2020 -0500
[DO NOT MERGE] Adds fling gesture suppression to Launcher
Test: Tested locally
BUG: 150688842
Change-Id: Ifa96bd01363de47cf1d8cdce34d81d525c8c2c04
(cherry picked from commit 9b90b1b0345ea57a6152919d318f4ce9cacd7556)
commit 631ed598ee115bdfa1b3249a87c1f266eb93d57d
Merge: 9a32222ce 9b90b1b03
Author: Govinda Wasserman <gwasserman@google.com>
Date: Wed Mar 11 18:19:26 2020 +0000
Merge "[DO NOT MERGE] Adds fling gesture suppression to Launcher" into ub-launcher3-qt-qpr1-dev
commit 9b90b1b0345ea57a6152919d318f4ce9cacd7556
Author: Govinda Wasserman <gwasserman@google.com>
Date: Thu Mar 5 16:50:22 2020 -0500
[DO NOT MERGE] Adds fling gesture suppression to Launcher
Test: Tested locally
BUG: 150688842
Change-Id: Ifa96bd01363de47cf1d8cdce34d81d525c8c2c04
commit 9c40c83d70bbe4689e1bddc012aec6d7a04dc490
Merge: b41aa64b8 86ace5452
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Tue Mar 10 20:55:30 2020 +0000
[automerger skipped] Merge "Dismisses system overlays for Home intent." into ub-launcher3-qt-future-dev am: 86ace54523 -s ours
am skip reason: Change-Id Ib9c85de2f83f99d1ef53fb17fde5d0b3c514849a with SHA-1 65ced1b1d0 is in history
Change-Id: I70904c253ac6ad36820069f64d9338ee067d159b
commit b41aa64b8df2256f554c7ea4fa96c10464d1a7b6
Merge: d7c844167 5e72945a8
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Tue Mar 10 20:55:18 2020 +0000
[automerger skipped] Merge "Import translations. DO NOT MERGE" into ub-launcher3-rvc-dev am: 5e72945a85 -s ours
am skip reason: subject contains skip directive
Change-Id: Ib8b27aa6ad4e4f0ed4bdebf3bb7b7cac654fad25
commit d7c84416765d4883fa2d8bba595c058db042325f
Merge: 0a9471546 3c1db273b
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Tue Mar 10 19:15:37 2020 +0000
Merge "DeviceFlag change is not detected when phenotype updates." into ub-launcher3-master
commit 5e72945a852ba56976835874a7c012726d2e00d6
Merge: a066cb443 a159b77ef
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Mar 10 17:45:51 2020 +0000
Merge "Import translations. DO NOT MERGE" into ub-launcher3-rvc-dev
commit 86ace54523dc354fadd65987e5ace43f89586e34
Merge: 44e729895 b3b8aefe5
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Mar 10 17:42:54 2020 +0000
Merge "Dismisses system overlays for Home intent." into ub-launcher3-qt-future-dev
commit a159b77ef149652daf56e4f413fd4dbc264b032b
Author: Bill Yi <byi@google.com>
Date: Tue Mar 10 09:56:26 2020 -0700
Import translations. DO NOT MERGE
Auto-generated-cl: translation import
Change-Id: I4d6b82f9e793cf2649102e913c81c27f6ccc004a
commit 0a947154691988747aae405018c8ddb36be27a05
Merge: f2508e783 44e729895
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Tue Mar 10 16:21:57 2020 +0000
[automerger skipped] Merge "Import translations. DO NOT MERGE" into ub-launcher3-qt-future-dev am: 44e7298953 -s ours
am skip reason: subject contains skip directive
Change-Id: I1b060550e9d0f839c96b1582712321ef530e353f
commit 44e729895391cd49acc938004c6044fc652ea9db
Merge: defd3c0e6 83730697f
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Mar 10 16:18:29 2020 +0000
Merge "Import translations. DO NOT MERGE" into ub-launcher3-qt-future-dev
commit f2508e783f624f423bb892a0060d84cbfc36052f
Merge: e635a2689 defd3c0e6
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Tue Mar 10 16:16:51 2020 +0000
[automerger skipped] Import translations. DO NOT MERGE am: 9a32222ce7 -s ours am: defd3c0e6f -s ours
am skip reason: subject contains skip directive
Change-Id: I5eb69f5179f420f1ab678a025ab7bf28f93f595a
commit defd3c0e6fc964cbbb5801cf045bd6de9689a0c7
Merge: 94c993a63 9a32222ce
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Tue Mar 10 16:04:16 2020 +0000
[automerger skipped] Import translations. DO NOT MERGE am: 9a32222ce7 -s ours
am skip reason: subject contains skip directive
Change-Id: I4fe5c187cc89f91199f3265f43d323556a992841
commit e635a2689d0b75065a7a8d5fae031382137303a6
Merge: dbcc63ede a066cb443
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Tue Mar 10 16:04:12 2020 +0000
[automerger skipped] Import translations. DO NOT MERGE am: a066cb4430 -s ours
am skip reason: subject contains skip directive
Change-Id: I2cd255fd2f265718df096d05f3c8a67c78662c58
commit dbcc63edebe373d5f698e7d404eff83898a5b8b2
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Fri Mar 6 15:35:55 2020 -0800
Removing some autoboxing during property animation
Change-Id: Ibd6f20c565a4d66dc6d606b3f0bbc96fec66fe56
commit add170098c5696948edbd7d7e3f220c801cfc9eb
Merge: d01ee6d6a 4c9ee6354
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Tue Mar 10 08:43:41 2020 +0000
Merge "Converting some anonymous classes to lambda calls" into ub-launcher3-master
commit b3b8aefe5751bf55ef32f61148f2a9b6c811db9e
Author: Andy Wickham <awickham@google.com>
Date: Tue Mar 10 01:36:02 2020 +0000
Dismisses system overlays for Home intent.
Test: Used Facebook chatheads (not system bubble).
Before the change, Home gesture didn't work. After
the change, it does work :)
Bug: 146593239
Merged-In: Ib9c85de2f83f99d1ef53fb17fde5d0b3c514849a
Change-Id: I19d91aaed19ccaec68478e364ce6b80049d49a98
commit a066cb4430989496e80770bbac04c68b2d515f2b
Author: Bill Yi <byi@google.com>
Date: Mon Mar 9 19:41:30 2020 -0700
Import translations. DO NOT MERGE
Auto-generated-cl: translation import
Change-Id: I91780ce30d4eb9825d415e2825b9a94e2a4fade8
commit 83730697f8416f8124bb0a6593aba5334f38546e
Author: Bill Yi <byi@google.com>
Date: Mon Mar 9 19:37:07 2020 -0700
Import translations. DO NOT MERGE
Auto-generated-cl: translation import
Change-Id: I833e37768c8b22a17cd5e36ac7b01ac024f3bbfc
commit 9a32222ce76b37911e42fdfa500d3e47623d459d
Author: Bill Yi <byi@google.com>
Date: Mon Mar 9 19:32:09 2020 -0700
Import translations. DO NOT MERGE
Auto-generated-cl: translation import
Change-Id: I4cb33e7020ee7cb582982fecba72dfd7f2c70469
commit d01ee6d6a84ca0fcc2dbc4757cdc90e36da02692
Merge: 7dfe1360e 94c993a63
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Tue Mar 10 01:05:38 2020 +0000
[automerger skipped] Merge "[DO NOT MERGE] Fix some visual jumps when swiping home" into ub-launcher3-qt-future-dev am: 94c993a635 -s ours
am skip reason: subject contains skip directive
Change-Id: I2b30725cd240cdc7ebfcb50eb2180aaf81ab4267
commit 94c993a635f00796b00c45b47b29496fe891839c
Merge: e1664fcf1 8caa78790
Author: Jonathan Miranda <jonmiranda@google.com>
Date: Tue Mar 10 00:49:17 2020 +0000
Merge "[DO NOT MERGE] Fix some visual jumps when swiping home" into ub-launcher3-qt-future-dev
commit 7dfe1360edbe9ea64c0b8c591ffc525ba9a5a581
Merge: 590914cc5 79a352169
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Tue Mar 10 00:46:11 2020 +0000
Merge "Show drag handle indictor in 2 zone model" into ub-launcher3-master
commit 79a352169f0197f5d0ea2be32cd8cf2f7dbef1ad
Author: Tony Wickham <twickham@google.com>
Date: Mon Mar 9 16:31:21 2020 -0700
Show drag handle indictor in 2 zone model
Note this is just the tiny arrow we show in accessibility mode, will
probably need to get some updated visual treatment going forward.
Bug: 143361609
Change-Id: I65975727f101984429aadc35a650826e36d9c9aa
commit 590914cc5ec219590f56cea52d975865187e7e0e
Merge: e9801665a 65ced1b1d
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Mon Mar 9 22:00:20 2020 +0000
Merge "Dismisses system overlays for Home intent." into ub-launcher3-master
commit e9801665ac81ac0d4059991377a3a782f92dfe66
Merge: b365cc438 e1664fcf1
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Mon Mar 9 21:42:07 2020 +0000
[automerger skipped] [DO NOT MERGE] Initiailize APP_SEARCH_IMRPOVEMENT flag outside DEBUG builds. am: e1664fcf14 -s ours
am skip reason: subject contains skip directive
Change-Id: I06a7e3ef6f5a0d6ed852b0b32238426ca3ba3809
commit 3c1db273bec12d4bb0573e20bc0f6ecc5b7e18b7
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Sat Mar 7 23:52:17 2020 -0800
DeviceFlag change is not detected when phenotype updates.
Bug: 151025334
Test: adb shell device_config put launcher FOLDER_NAME_SUGGEST false
Change-Id: I5e478aebfea5847cf7cbe7c0cb6cb0c6f81481cb
commit b365cc43878e0cb4e6d4b998c10cd590adc4cb8d
Author: Tony Wickham <twickham@google.com>
Date: Mon Mar 9 13:20:04 2020 -0700
Fix crash when dumping before user unlocks
Bug: 150864182
Bug: 151050221
Change-Id: I29ba2ef66b4359a47f866d02306498537c45236e
commit e1664fcf1486d337ea998ded70d092590a0bfbd9
Author: Alex Mang <alexmang@google.com>
Date: Mon Mar 9 12:57:11 2020 -0700
[DO NOT MERGE] Initiailize APP_SEARCH_IMRPOVEMENT flag outside DEBUG builds.
This is addressing a bug where flags are only changed on debug build
devices or initially when changes. When nexuslauncher restarts the flag
is no longer retrieved.
Change-Id: Ieb6f460a271c918ee4e493b34692244f39cb3740
commit 8caa787906b49427afed77e7bd63b72d9bbbe8a7
Author: Jon Miranda <jonmiranda@google.com>
Date: Mon Mar 9 12:50:38 2020 -0700
[DO NOT MERGE] Fix some visual jumps when swiping home
All caused by running the transform progress from 0 to 1 instead of
starting at whatever the progress was before ending the gesture, e.g.:
- When swiping to home without animating into an icon, the corner radius
was set back to the window corner radius.
- Before this change, the clip didn't update throughout the animation,
making the window slightly bigger than the floating icon view; after
this change, the clip jumped to show the insets again before clipping
back down during the home animation.
Partial backport of ag/Ie48f4b665a5bf3cbef76bdf7f043febe99fb84a0
Bug: 150680980
Change-Id: Ida65097f0ef7d2e11d48b84ecdd353ef89078015
commit bf48cd480cd131c370760117681917dedd784c51
Author: Andy Wickham <awickham@google.com>
Date: Tue Mar 3 01:15:27 2020 +0000
Removes iconloaderlib from Launcher3.
(It's now in frameworks/libs/systemui)
Bug: 138964382
Test: builds
Change-Id: Ic60adfb2ebdcf1a72b440df26023b861fd6e62d5
commit a9bcd82554534d55e38ca039eb52c1dfacbdb70a
Merge: e6df7da2a cfaa4889e
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Sat Mar 7 08:38:49 2020 +0000
Merge "Enabling springs for start dismiss animation" into ub-launcher3-master
commit e6df7da2a252c5d57114346d6bf6d6883b4a9f9a
Merge: df8232c22 16eca5500
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Sat Mar 7 07:19:13 2020 +0000
Merge "Enabling event verification for Launcher3" into ub-launcher3-master
commit cfaa4889e65190b40ea988dd03421a01c9e06abc
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Tue Feb 25 14:37:01 2020 -0800
Enabling springs for start dismiss animation
> Adding flag support for PendingAnimation which can be used
to define custom behavior for various animations
> Using SpringAnimationBuild for spring animation instead of
SpringObjectanimator
Change-Id: I41ca34b0574981bb3fc7894639a321c12e6feac1
commit df8232c2242eeb2d8efc050a5e7afb88782ed9ca
Merge: cfea0fb34 f538393e4
Author: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Date: Sat Mar 7 06:50:59 2020 +0000
[automerger skipped] [DO NOT MERGE] Turn off FOLDER_NAME_SUGGEST feature flag am: f538393e42 -s ours
am skip reason: subject contains skip directive
Change-Id: I1bbffc4f9582d106ce28ac07e6c6719c9f8f063d
commit f538393e42896e5488ca10fccc4cf9409d7295e2
Author: Hyunyoung Song <hyunyoungs@google.com>
Date: Fri Mar 6 12:53:42 2020 -0800
[DO NOT MERGE] Turn off FOLDER_NAME_SUGGEST feature flag
Bug: 150788630
Change-Id: I740d6b6f3ee1a33a95debfafa29b3caea24a03c3
commit 65ced1b1d00bc6a6713b442162020df31d497f54
Author: Andy Wickham <awickham@google.com>
Date: Sat Mar 7 02:14:19 2020 +0000
Dismisses system overlays for Home intent.
Test: Used Facebook chatheads (not system bubble).
Before the change, Home gesture didn't work. After
the change, it does work :)
Fixes: 146593239
Change-Id: Ib9c85de2f83f99d1ef53fb17fde5d0b3c514849a
commit cfea0fb348910bb245a03df0e178f0be59905dc7
Merge: 312340504 e7dd35ef0
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Sat Mar 7 00:20:56 2020 +0000
Merge "OverviewActions: Renaming overview_actions_container and setActionsView" into ub-launcher3-master
commit 31234050473d511a61ce4dc257c15281c1c886b4
Merge: f655f5cd1 27409e23d
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Fri Mar 6 23:44:57 2020 +0000
Merge "Removing SecondaryDisplayLauncher library as it is directly included in Launcher" into ub-launcher3-master
commit f655f5cd16a9579afc11088e5545148261225b9f
Merge: a579ddc9c 8687bc213
Author: Winson Chung <winsonc@google.com>
Date: Fri Mar 6 23:33:26 2020 +0000
Merge "Initial changes to support blur" into ub-launcher3-master
commit 4c9ee63540dcd2c039831edb0816a56458e30f8f
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Fri Mar 6 15:16:22 2020 -0800
Converting some anonymous classes to lambda calls
Change-Id: I386046a4a515d84801a8bbd11cfa090ba7adfd71
commit 8687bc2131ce98ea0d058290dd21f03b5a429c82
Author: Winson Chung <winsonc@google.com>
Date: Thu Feb 27 23:34:24 2020 -0800
Initial changes to support blur
- Add a new controller to update the background blur on either the
launcher or app surfaces based on state or transition
Bug: 149792636
Change-Id: I6103cd3d53a00c8025558dd49bb73137e2980014
commit a579ddc9c813f314ab3dfd4e80a9c0cf1c77ec61
Author: Samuel Fufa <sfufa@google.com>
Date: Thu Feb 27 16:59:19 2020 -0800
Refactor logging to capture Target hierarchy
Instead of creating a fixed number of targets, we now pass an ArrayList
of targets to. Any class implementing
LogContainerProviders#fillInLogContainerData can setup it's own target
and add it to the ArrayList, It can also pass the ArrayList to other
LogContainerProvider to capture full Target hierarchy.
Bug: 147305863
Change-Id: I0063c692120fb9e1cff2d8902c5da972d0623418
commit e7dd35ef0dd653764e57665756e33ce75446e520
Author: Sreyas <sreyasr@google.com>
Date: Fri Mar 6 10:50:43 2020 -0800
OverviewActions: Renaming overview_actions_container and setActionsView
Change-Id: Ie444101f246e0d00980b47ce39f6e74dade23f73
commit 9099dfcfb75dff987c157a659de5883fe92b22c4
Merge: e90adc47e 04b90c0fc
Author: Vadim Tryshev <vadimt@google.com>
Date: Fri Mar 6 18:36:05 2020 +0000
Merge "Test tweaks for the memory activity recreation test" into ub-launcher3-master
commit e90adc47ef114e129a14382f30d84910ef394348
Author: Winson Chung <winsonc@google.com>
Date: Fri Mar 6 00:01:49 2020 -0800
Fallback to predefined orientation handler if recents view isn't available
Change-Id: Iaed42fb9ef598d65e1cf2d166cc343f888352d47
commit d9da5a45fd1653c14e6fd54b15708a696c43c037
Merge: f67ab6c64 3abc8511a
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Fri Mar 6 06:53:51 2020 +0000
Merge "Enabling widget config tests after fixing flakiness" into ub-launcher3-master
commit 16eca5500dab2f253f52920edaed477f5e43f413
Author: vadimt <vadimt@google.com>
Date: Thu Mar 5 19:02:18 2020 -0800
Enabling event verification for Launcher3
Will help investigating otherwise mysterious failures.
Change-Id: I805ed136baf6d86921fdb4782304fcdafbd3af5c
commit f67ab6c64d0691f7ff1ede3942179172122dac1e
Merge: 93648b0a5 bb2bf277c
Author: TreeHugger Robot <treehugger-gerrit@google.com>
Date: Fri Mar 6 02:14:19 2020 +0000
Merge "Catching everything from dumpHprofData" into ub-launcher3-master
commit bb2bf277c0b22ecb1dc64d9971978d3c958dda44
Author: vadimt <vadimt@google.com>
Date: Thu Mar 5 17:22:18 2020 -0800
Catching everything from dumpHprofData
Change-Id: I79ced1d4bb3e6ea43ce6fa5bd07fe22b577006f9
commit 3abc8511a51afe983b481bdf6631535aa8b94f28
Author: vadimt <vadimt@google.com>
Date: Thu Mar 5 14:01:23 2020 -0800
Enabling widget config tests after fixing flakiness
Bug: 148867106
Change-Id: I8bbd9ef9b1ca574f79f3f76869051495b59734ce
commit 27409e23d1ba7ca94031de8ff0603c53b86cfcdc
Author: Sunny Goyal <sunnygoyal@google.com>
Date: Wed Feb 26 16:08:20 2020 -0800
Removing SecondaryDisplayLauncher library as it is directly included in Launcher
Change-Id: I97a1fad07f2f6d34fc31c720fcc1e03d0f56477e
commit 04b90c0fcb316c830f7dc66475ff85c6760402f5
Author: vadimt <vadimt@google.com>
Date: Tue Oct 15 10:47:51 2019 -0700
Test tweaks for the memory activity recreation test
Speeding up switching navigation mode by switching from latch
(which is not fired) to polling. I'll figure out later why the latch
doesn't work.
Bug: 139137636
Change-Id: I28a9b2b9a3882919fd2a3280b9804afe1b44a46e
@@ -34,6 +34,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:layout_gravity="center_vertical"
android:textColor="@android:color/white"
android:textSize="16sp"/>
@@ -14,8 +14,5 @@
limitations under the License.
-->
<resources>
<integer name="app_background_blur_radius">150</integer>
<integer name="allapps_background_blur_radius">90</integer>
<integer name="overview_background_blur_radius">50</integer>
<integer name="folder_background_blur_radius_adjustment">20</integer>
<integer name="max_depth_blur_radius">150</integer>
</resources>
@@ -0,0 +1,151 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import android.content.Context;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.os.Handler;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.TriangleShape;
/**
* A base class for arrow tip view in launcher
*/
public class ArrowTipView extends AbstractFloatingView {
private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
private static final long SHOW_DELAY_MS = 200;
private static final long SHOW_DURATION_MS = 300;
private static final long HIDE_DURATION_MS = 100;
protected final Launcher mLauncher;
private final Handler mHandler = new Handler();
private Runnable mOnClosed;
public ArrowTipView(Context context) {
super(context, null, 0);
mLauncher = Launcher.getLauncher(context);
init(context);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
close(true);
}
return false;
}
@Override
protected void handleClose(boolean animate) {
if (mIsOpen) {
if (animate) {
animate().alpha(0f)
.withLayer()
.setStartDelay(0)
.setDuration(HIDE_DURATION_MS)
.setInterpolator(Interpolators.ACCEL)
.withEndAction(() -> mLauncher.getDragLayer().removeView(this))
.start();
} else {
animate().cancel();
mLauncher.getDragLayer().removeView(this);
}
if (mOnClosed != null) mOnClosed.run();
mIsOpen = false;
}
}
@Override
public void logActionCommand(int command) {
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ON_BOARD_POPUP) != 0;
}
private void init(Context context) {
inflate(context, R.layout.arrow_toast, this);
setOrientation(LinearLayout.VERTICAL);
View dismissButton = findViewById(R.id.dismiss);
dismissButton.setOnClickListener(view -> {
handleClose(true);
});
View arrowView = findViewById(R.id.arrow);
ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
arrowLp.width, arrowLp.height, false));
Paint arrowPaint = arrowDrawable.getPaint();
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
// The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
arrowPaint.setPathEffect(new CornerPathEffect(
context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
arrowView.setBackground(arrowDrawable);
mIsOpen = true;
mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
}
/**
* Show Tip with specified string and Y location
*/
public ArrowTipView show(String text, int top) {
((TextView) findViewById(R.id.text)).setText(text);
mLauncher.getDragLayer().addView(this);
DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
params.gravity = Gravity.CENTER_HORIZONTAL;
params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left;
params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right;
post(() -> setY(top - getHeight()));
setAlpha(0);
animate()
.alpha(1f)
.withLayer()
.setStartDelay(SHOW_DELAY_MS)
.setDuration(SHOW_DURATION_MS)
.setInterpolator(Interpolators.DEACCEL)
.start();
return this;
}
/**
* Register a callback fired when toast is hidden
*/
public ArrowTipView setOnClosedCallback(Runnable runnable) {
mOnClosed = runnable;
return this;
}
}
@@ -91,7 +91,7 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
AppWindowAnimationHelper helper =
new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher);
Animator recentsAnimator = getRecentsWindowAnimator(taskView, skipLauncherChanges,
appTargets, wallpaperTargets, mLauncher.getBackgroundBlurController(), helper);
appTargets, wallpaperTargets, mLauncher.getDepthController(), helper);
anim.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
Animator childStateAnimation = null;
@@ -16,133 +16,30 @@
package com.android.launcher3.appprediction;
import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
import android.content.Context;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.os.Handler;
import android.os.UserManager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.ArrowTipView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.TriangleShape;
import com.android.systemui.shared.system.LauncherEventUtil;
/**
* All apps tip view aligned just above prediction apps, shown to users that enter all apps for the
* ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
* first time.
*/
public class AllAppsTipView extends AbstractFloatingView {
public class AllAppsTipView {
private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen";
private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
private static final long SHOW_DELAY_MS = 200;
private static final long SHOW_DURATION_MS = 300;
private static final long HIDE_DURATION_MS = 100;
private final Launcher mLauncher;
private final Handler mHandler = new Handler();
private AllAppsTipView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(LinearLayout.VERTICAL);
mLauncher = Launcher.getLauncher(context);
init(context);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
close(true);
}
return false;
}
@Override
protected void handleClose(boolean animate) {
if (mIsOpen) {
if (animate) {
animate().alpha(0f)
.withLayer()
.setStartDelay(0)
.setDuration(HIDE_DURATION_MS)
.setInterpolator(Interpolators.ACCEL)
.withEndAction(() -> mLauncher.getDragLayer().removeView(this))
.start();
} else {
animate().cancel();
mLauncher.getDragLayer().removeView(this);
}
mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
mIsOpen = false;
}
}
@Override
public void logActionCommand(int command) {
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_ON_BOARD_POPUP) != 0;
}
private void init(Context context) {
inflate(context, R.layout.arrow_toast, this);
TextView textView = findViewById(R.id.text);
textView.setText(R.string.all_apps_prediction_tip);
View dismissButton = findViewById(R.id.dismiss);
dismissButton.setOnClickListener(view -> {
mLauncher.getUserEventDispatcher().logActionTip(
LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS);
handleClose(true);
});
View arrowView = findViewById(R.id.arrow);
ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
arrowLp.width, arrowLp.height, false));
Paint arrowPaint = arrowDrawable.getPaint();
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
// The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
arrowPaint.setPathEffect(new CornerPathEffect(
context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
arrowView.setBackground(arrowDrawable);
mIsOpen = true;
mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
}
private static boolean showAllAppsTipIfNecessary(Launcher launcher) {
FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView();
@@ -156,28 +53,15 @@ public class AllAppsTipView extends AbstractFloatingView {
return false;
}
AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(),
null);
launcher.getDragLayer().addView(allAppsTipView);
int[] coords = new int[2];
floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords);
ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> {
launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS,
ALL_APPS_PREDICTION_TIPS);
});
arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams();
params.gravity = Gravity.CENTER_HORIZONTAL;
int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop();
allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize(
R.dimen.all_apps_tip_bottom_margin));
allAppsTipView.setAlpha(0);
allAppsTipView.animate()
.alpha(1f)
.withLayer()
.setStartDelay(SHOW_DELAY_MS)
.setDuration(SHOW_DURATION_MS)
.setInterpolator(Interpolators.DEACCEL)
.start();
launcher.getUserEventDispatcher().logActionTip(
LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS);
return true;
}
@@ -23,23 +23,28 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.app.NotificationCompat;
import com.android.launcher3.CellLayout;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.config.FeatureFlags;
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 java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
/**
@@ -52,48 +57,179 @@ public class HotseatEduController {
private static final int ONBOARDING_NOTIFICATION_ID = 7641;
private final Launcher mLauncher;
private final NotificationManager mNotificationManager;
private final Notification mNotification;
private List<WorkspaceItemInfo> mPredictedApps;
private HotseatEduDialog mActiveDialog;
private final NotificationManager mNotificationManager;
private final Notification mNotification;
private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
private IntArray mNewScreens = null;
private Runnable mOnOnboardingComplete;
HotseatEduController(Launcher launcher) {
HotseatEduController(Launcher launcher, Runnable runnable) {
mLauncher = launcher;
mOnOnboardingComplete = runnable;
mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
createNotificationChannel();
mNotification = createNotification();
}
boolean migrate() {
Workspace workspace = mLauncher.getWorkspace();
CellLayout firstScreen = workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID);
int toPage = Workspace.FIRST_SCREEN_ID;
int toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
toPage = workspace.getScreenIdForPageIndex(workspace.getPageCount());
toRow = 0;
} else if (!firstScreen.makeSpaceForHotseatMigration(true)) {
return false;
/**
* Checks what type of migration should be used and migrates hotseat
*/
void migrate() {
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
migrateToFolder();
} else {
migrateHotseatWhole();
}
ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets();
for (int i = 0; i < hotseatVG.getChildCount(); i++) {
View child = hotseatVG.getChildAt(i);
}
/**
* This migration places all non folder items in the hotseat into a folder and then moves
* all folders in the hotseat to a workspace page that has enough empty spots.
*
* @return pageId that has accepted the items.
*/
private int migrateToFolder() {
ArrayDeque<FolderInfo> folders = new ArrayDeque<>();
ArrayList<WorkspaceItemInfo> putIntoFolder = new ArrayList<>();
//separate folders and items that can get in folders
for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
View view = mLauncher.getHotseat().getChildAt(i, 0);
if (view == null) continue;
ItemInfo info = (ItemInfo) view.getTag();
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
folders.add((FolderInfo) info);
} else if (info instanceof WorkspaceItemInfo) {
putIntoFolder.add((WorkspaceItemInfo) info);
}
}
// create a temp folder and add non folder items to it
if (!putIntoFolder.isEmpty()) {
ItemInfo firstItem = putIntoFolder.get(0);
FolderInfo folderInfo = new FolderInfo();
folderInfo.title = "";
mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
firstItem.screenId, firstItem.cellX, firstItem.cellY);
folderInfo.contents.addAll(putIntoFolder);
for (int i = 0; i < folderInfo.contents.size(); i++) {
ItemInfo item = folderInfo.contents.get(i);
item.rank = i;
mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0,
item.cellX, item.cellY);
}
folders.add(folderInfo);
}
mNewItems.addAll(folders);
return placeFoldersInWorkspace(folders);
}
private int placeFoldersInWorkspace(ArrayDeque<FolderInfo> folders) {
if (folders.isEmpty()) return 0;
Workspace workspace = mLauncher.getWorkspace();
InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()];
for (int i = 0; i < occupancyList.length; i++) {
occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy();
}
//scan every screen to find available spots to place folders
int occupancyIndex = 0;
int[] itemXY = new int[2];
while (occupancyIndex < occupancyList.length && !folders.isEmpty()) {
GridOccupancy occupancy = occupancyList[occupancyIndex];
if (occupancy.findVacantCell(itemXY, 1, 1)) {
FolderInfo info = folders.poll();
mLauncher.getModelWriter().moveItemInDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]);
occupancy.markCells(info, true);
} else {
occupancyIndex++;
}
}
if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex);
int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
// if all screens are full and we still have folders left, put those on a new page
FolderInfo folderInfo;
int col = 0;
while ((folderInfo = folders.poll()) != null) {
mLauncher.getModelWriter().moveItemInDatabase(folderInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++,
idp.numRows - 1);
}
mNewScreens = IntArray.wrap(screenId);
return workspace.getPageCount();
}
/**
* This migration option attempts to move the entire hotseat up to the first workspace that
* has space to host items. If no such page is found, it moves items to a new page.
*
* @return pageId where items are migrated
*/
private int migrateHotseatWhole() {
Workspace workspace = mLauncher.getWorkspace();
Hotseat hotseatVG = mLauncher.getHotseat();
int pageId = -1;
int toRow = 0;
for (int i = 0; i < workspace.getPageCount(); i++) {
CellLayout target = workspace.getScreenWithId(workspace.getScreenIdForPageIndex(i));
if (target.makeSpaceForHotseatMigration(true)) {
toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
pageId = i;
break;
}
}
if (pageId == -1) {
pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
}
for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
View child = hotseatVG.getChildAt(i, 0);
if (child == null || child.getTag() == null) continue;
ItemInfo tag = (ItemInfo) child.getTag();
mLauncher.getModelWriter().moveItemInDatabase(tag,
LauncherSettings.Favorites.CONTAINER_DESKTOP, toPage, tag.screenId, toRow);
LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow);
mNewItems.add(tag);
}
return true;
return pageId;
}
void removeNotification() {
mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
}
void finishOnboarding() {
mLauncher.getModel().rebindCallbacks();
mLauncher.getHotseat().removeAllViewsInLayout();
if (!mNewItems.isEmpty()) {
int lastPage = mNewItems.get(mNewItems.size() - 1).screenId;
ArrayList<ItemInfo> animated = new ArrayList<>();
ArrayList<ItemInfo> nonAnimated = new ArrayList<>();
for (ItemInfo info : mNewItems) {
if (info.screenId == lastPage) {
animated.add(info);
} else {
nonAnimated.add(info);
}
}
mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated);
}
mOnOnboardingComplete.run();
destroy();
mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
removeNotification();
}
void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
@@ -29,6 +29,7 @@ import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.android.launcher3.ArrowTipView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
@@ -53,20 +54,14 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
private static final int DEFAULT_CLOSE_DURATION = 200;
protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
private static final int MIGRATE_SAME_PAGE = 0;
private static final int MIGRATE_NEW_PAGE = 1;
private static final int MIGRATE_NO_MIGRATE = 2;
// we use this value to keep track of migration logs as we experiment with different migrations
private static final int MIGRATION_EXPERIMENT_IDENTIFIER = 1;
private final Rect mInsets = new Rect();
private View mHotseatWrapper;
private CellLayout mSampleHotseat;
private TextView mEduHeading;
private TextView mEduContent;
private Button mDismissBtn;
private int mMigrationMode = MIGRATE_SAME_PAGE;
public void setHotseatEduController(HotseatEduController hotseatEduController) {
mHotseatEduController = hotseatEduController;
}
@@ -89,8 +84,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
super.onFinishInflate();
mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
mSampleHotseat = findViewById(R.id.sample_prediction);
mEduHeading = findViewById(R.id.hotseat_edu_heading);
mEduContent = findViewById(R.id.hotseat_edu_content);
DeviceProfile grid = mLauncher.getDeviceProfile();
Rect padding = grid.getHotseatLayoutPadding();
@@ -105,25 +98,30 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
mDismissBtn = findViewById(R.id.no_thanks);
mDismissBtn.setOnClickListener(this::onDismiss);
// update ui to reflect which migration method is going to be used
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
((TextView) findViewById(R.id.hotseat_edu_content)).setText(
R.string.hotseat_edu_message_migrate_alt);
}
}
private void onAccept(View v) {
if (mMigrationMode == MIGRATE_NO_MIGRATE || !mHotseatEduController.migrate()) {
onDismiss(v);
return;
}
mHotseatEduController.migrate();
handleClose(true);
mHotseatEduController.finishOnboarding();
logUserAction(true);
int toastStringRes = mMigrationMode == MIGRATE_SAME_PAGE
//TODO: pass actual page index here.
// Temporarily we're passing 1 for folder migration and 2 for page migration
logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
int toastStringRes = !FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()
? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt;
Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show();
}
private void onDismiss(View v) {
Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
int top = mLauncher.getHotseat().getTop();
new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top);
mHotseatEduController.finishOnboarding();
logUserAction(false);
logUserAction(false, -1);
handleClose(true);
}
@@ -155,7 +153,7 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
}
private void logUserAction(boolean migrated) {
private void logUserAction(boolean migrated, int pageIndex) {
LauncherLogProto.Action action = new LauncherLogProto.Action();
LauncherLogProto.Target target = new LauncherLogProto.Target();
action.type = LauncherLogProto.Action.Type.TOUCH;
@@ -164,8 +162,9 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
: HYBRID_HOTSEAT_CANCELED;
target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
// encoding migration type on pageIndex
target.pageIndex = mMigrationMode;
target.pageIndex = pageIndex;
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
}
@@ -218,15 +217,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
}
}
@Override
protected void attachToContainer() {
super.attachToContainer();
if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
mEduContent.setText(R.string.hotseat_edu_message_migrate_alt);
mMigrationMode = MIGRATE_NEW_PAGE;
}
}
/**
* Opens User education dialog with a list of suggested apps
*/
@@ -94,7 +94,6 @@ public class HotseatPredictionController implements DragController.DragListener,
private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps";
private static final String PREDICTION_CLIENT = "hotseat";
private DropTarget.DragObject mDragObject;
private int mHotSeatItemsCount;
private int mPredictedSpotsCount = 0;
@@ -115,6 +114,7 @@ public class HotseatPredictionController implements DragController.DragListener,
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
private final View.OnLongClickListener mPredictionLongClickListener = v -> {
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
if (mLauncher.getWorkspace().isSwitchingState()) return false;
@@ -276,12 +276,10 @@ public class HotseatPredictionController implements DragController.DragListener,
.build());
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
setPauseUIUpdate(false);
if (!isReady()) {
if (mHotseatEduController != null) {
mHotseatEduController.destroy();
}
mHotseatEduController = new HotseatEduController(mLauncher);
mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
}
mAppPredictor.requestPredictionUpdate();
}
@@ -327,7 +325,7 @@ public class HotseatPredictionController implements DragController.DragListener,
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
predictionLog.append("]");
FileLog.d(TAG, predictionLog.toString());
if (false) FileLog.d(TAG, predictionLog.toString());
updateDependencies();
if (isReady()) {
fillGapsWithPrediction();
@@ -488,7 +486,6 @@ public class HotseatPredictionController implements DragController.DragListener,
}
}
@Override
public void onDragEnd() {
if (mDragObject == null) {
@@ -564,7 +561,8 @@ public class HotseatPredictionController implements DragController.DragListener,
}
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) {}
public void reapplyItemInfo(ItemInfoWithIcon info) {
}
@Override
public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
@@ -19,7 +19,6 @@ import android.content.Context;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.LayoutUtils;
@@ -107,7 +106,7 @@ public class BackgroundAppState extends OverviewState {
}
@Override
public int getBackgroundBlurRadius(Context context) {
return context.getResources().getInteger(R.integer.app_background_blur_radius);
public float getDepth(Context context) {
return 1f;
}
}
@@ -207,8 +207,8 @@ public class OverviewState extends LauncherState {
}
@Override
public int getBackgroundBlurRadius(Context context) {
return context.getResources().getInteger(R.integer.overview_background_blur_radius);
public float getDepth(Context context) {
return 1f;
}
@Override
@@ -120,8 +120,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
mPeekAnim.start();
VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
peekDuration, 0);
mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(isPaused ? 0 : 1)
.setDuration(peekDuration).start();
}
/**
@@ -80,6 +80,9 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
}
private boolean canInterceptTouch() {
if (mCurrentAnimation != null) {
mCurrentAnimation.forceFinishIfCloseToEnd();
}
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
@@ -126,6 +129,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
TaskView view = mRecentsView.getTaskViewAt(i);
if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
.isEventOverView(view, ev)) {
// Disable swiping up and down if the task overlay is modal.
@@ -19,7 +19,7 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.launcher3.uioverrides.DepthController.DEPTH;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -34,7 +34,7 @@ import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.uioverrides.DepthController;
import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
import com.android.quickstep.util.RemoteAnimationProvider;
@@ -105,10 +105,10 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> exten
mRecentsView.setRunningTaskIconScaledDown(true);
}
BackgroundBlurController blurController = mActivityInterface.getBackgroundBlurController();
if (blurController != null) {
DepthController depthController = mActivityInterface.getDepthController();
if (depthController != null) {
// Update the surface to be the lowest closing app surface
blurController.setSurfaceToLauncher(mRecentsView);
depthController.setSurfaceToLauncher(mRecentsView);
}
AnimatorSet anim = new AnimatorSet();
@@ -124,7 +124,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> exten
if (mActivity == null) {
Log.e(TAG, "Animation created, before activity");
anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
.with(createBackgroundBlurAnimator(blurController));
.with(createDepthAnimator(depthController));
return anim;
}
@@ -136,7 +136,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> exten
if (runningTaskTarget == null) {
Log.e(TAG, "No closing app");
anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
.with(createBackgroundBlurAnimator(blurController));
.with(createDepthAnimator(depthController));
return anim;
}
@@ -184,7 +184,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> exten
});
}
anim.play(valueAnimator)
.with(createBackgroundBlurAnimator(blurController));
.with(createDepthAnimator(depthController));
return anim;
}
@@ -197,14 +197,14 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> exten
return RECENTS_LAUNCH_DURATION;
}
private Animator createBackgroundBlurAnimator(BackgroundBlurController blurController) {
if (blurController == null) {
private Animator createDepthAnimator(DepthController depthController) {
if (depthController == null) {
// Dummy animation
return ValueAnimator.ofInt(0);
}
return ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
BACKGROUND_APP.getBackgroundBlurRadius(mActivity),
OVERVIEW.getBackgroundBlurRadius(mActivity))
return ObjectAnimator.ofFloat(depthController, DEPTH,
BACKGROUND_APP.getDepth(mActivity),
OVERVIEW.getDepth(mActivity))
.setDuration(RECENTS_LAUNCH_DURATION);
}
}
@@ -56,8 +56,8 @@ import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.uioverrides.BackgroundBlurController.ClampedBlurProperty;
import com.android.launcher3.uioverrides.DepthController;
import com.android.launcher3.uioverrides.DepthController.ClampedDepthProperty;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -330,18 +330,17 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
endState.getVerticalProgress(activity)));
}
// Animate the blur
BackgroundBlurController blurController = getBackgroundBlurController();
int fromBlurRadius = fromState.getBackgroundBlurRadius(activity);
int toBlurRadius = endState.getBackgroundBlurRadius(activity);
Animator backgroundBlur = ObjectAnimator.ofInt(blurController,
new ClampedBlurProperty(toBlurRadius, fromBlurRadius),
fromBlurRadius, toBlurRadius);
anim.play(backgroundBlur);
// Animate the blur and wallpaper zoom
DepthController depthController = getDepthController();
float fromDepthRatio = fromState.getDepth(activity);
float toDepthRatio = endState.getDepth(activity);
Animator depthAnimator = ObjectAnimator.ofFloat(depthController,
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio);
anim.play(depthAnimator);
playScaleDownAnim(anim, activity, fromState, endState);
anim.setDuration(transitionLength * 2);
anim.setInterpolator(LINEAR);
AnimatorPlaybackController controller =
@@ -558,11 +557,11 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
@Nullable
@Override
public BackgroundBlurController getBackgroundBlurController() {
public DepthController getDepthController() {
BaseQuickstepLauncher launcher = getCreatedActivity();
if (launcher == null) {
return null;
}
return launcher.getBackgroundBlurController();
return launcher.getDepthController();
}
}
@@ -294,7 +294,7 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
}
setupRecentsViewUi();
mActivityInterface.getBackgroundBlurController().setSurfaceToLauncher(mRecentsView);
mActivityInterface.getDepthController().setSurfaceToLauncher(mRecentsView);
if (mDeviceState.getNavMode() == TWO_BUTTONS) {
// If the device is in two button mode, swiping up will show overview with predictions
@@ -186,7 +186,7 @@ public final class RecentsActivity extends BaseRecentsActivity {
AppWindowAnimationHelper helper = new AppWindowAnimationHelper(
mFallbackRecentsView.getPagedViewOrientedState(), this);
Animator recentsAnimator = getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
wallpaperTargets, null /* backgroundBlurController */,
wallpaperTargets, null /* depthController */,
helper);
target.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
@@ -19,7 +19,7 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP;
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.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.launcher3.uioverrides.DepthController.DEPTH;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
@@ -35,7 +35,7 @@ import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.uioverrides.DepthController;
import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.views.RecentsView;
@@ -123,7 +123,7 @@ public final class TaskViewUtils {
public static Animator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
BackgroundBlurController backgroundBlurController,
DepthController depthController,
final AppWindowAnimationHelper inOutHelper) {
SyncRtSurfaceTransactionApplierCompat applier =
new SyncRtSurfaceTransactionApplierCompat(v);
@@ -215,9 +215,9 @@ public final class TaskViewUtils {
}
});
if (backgroundBlurController != null) {
ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(backgroundBlurController,
BACKGROUND_BLUR, BACKGROUND_APP.getBackgroundBlurRadius(v.getContext()));
if (depthController != null) {
ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController,
DEPTH, BACKGROUND_APP.getDepth(v.getContext()));
animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
} else {
animatorSet.play(appAnimator);
@@ -15,7 +15,9 @@
*/
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.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -435,7 +437,8 @@ public class TouchInteractionService extends Service implements PluginListener<O
TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
mDeviceState.setOrientationTransformIfNeeded(event);
if (event.getAction() == ACTION_DOWN) {
final int action = event.getAction();
if (action == ACTION_DOWN) {
GestureState newGestureState = new GestureState(mOverviewComponentObserver,
ActiveGestureLog.INSTANCE.generateAndSetLogId());
newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
@@ -468,6 +471,13 @@ public class TouchInteractionService extends Service implements PluginListener<O
ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
mUncheckedConsumer.onMotionEvent(event);
if (action == ACTION_UP || action == ACTION_CANCEL) {
if (mConsumer != null && !mConsumer.isConsumerDetachedFromGesture()) {
onConsumerInactive(mConsumer);
}
}
TraceHelper.INSTANCE.endFlagsOverride(traceToken);
}
@@ -661,8 +671,8 @@ public class TouchInteractionService extends Service implements PluginListener<O
*/
private void onConsumerInactive(InputConsumer caller) {
if (mConsumer == caller) {
mConsumer = mResetGestureInputConsumer;
mUncheckedConsumer = mConsumer;
mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
mGestureState = new GestureState();
}
}
@@ -81,7 +81,8 @@ public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
if (!insets.equals(mInsets)) {
super.setInsets(insets);
}
setBackground(insets.top == 0 ? null
setBackground(insets.top == 0 || !mAllowSysuiScrims
? null
: Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
}
@@ -24,6 +24,11 @@ public abstract class DelegateInputConsumer implements InputConsumer {
mState = STATE_INACTIVE;
}
@Override
public boolean isConsumerDetachedFromGesture() {
return mDelegate.isConsumerDetachedFromGesture();
}
@Override
public boolean allowInterceptByParent() {
return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
@@ -160,6 +160,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
return TYPE_OTHER_ACTIVITY;
}
@Override
public boolean isConsumerDetachedFromGesture() {
return true;
}
private void forceCancelGesture(MotionEvent ev) {
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
@@ -126,6 +126,8 @@ public class StaggeredWorkspaceAnim {
addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
}
mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f)
.setDuration(ALPHA_DURATION_MS));
mAnimators.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -153,6 +155,10 @@ public class StaggeredWorkspaceAnim {
launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
}
public AnimatorSet getAnimators() {
return mAnimators;
}
/**
* Starts the animation.
*/
@@ -46,9 +46,9 @@ import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.uioverrides.DepthController;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.ScrimView;
@@ -409,7 +409,7 @@ public class LauncherRecentsView extends RecentsView<Launcher> implements StateL
}
@Override
protected BackgroundBlurController getBackgroundBlurController() {
return mActivity.getBackgroundBlurController();
protected DepthController getDepthController() {
return mActivity.getDepthController();
}
}
@@ -19,6 +19,7 @@ package com.android.quickstep.views;
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;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
@@ -29,7 +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.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.launcher3.uioverrides.DepthController.DEPTH;
import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
@@ -68,7 +69,6 @@ import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -88,7 +88,6 @@ import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
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.launcher3.anim.PendingAnimation.EndState;
@@ -96,11 +95,10 @@ import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.anim.SpringProperty;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.uioverrides.DepthController;
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;
@@ -720,6 +718,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
for (int i = 0; i < taskCount; i++) {
getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
}
if (mActionsView != null) {
mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE);
}
}
private void updateTaskStackListenerState() {
@@ -1165,7 +1166,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) {
anim.add(ObjectAnimator.ofFloat(taskView, ALPHA, 0).setDuration(duration), ACCEL_2);
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// alpha is set to 0 so that it can be recycled in the view pool properly
anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
FloatProperty<View> secondaryViewTranslate =
mOrientationHandler.getSecondaryViewTranslate();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -1257,9 +1260,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
if (needsCurveUpdates) {
ValueAnimator va = ValueAnimator.ofFloat(0, 1);
va.addUpdateListener((a) -> updateCurveProperties());
anim.add(va);
anim.addOnFrameCallback(this::updateCurveProperties);
}
// Add a tiny bit of translation Z, so that it draws on top of other views
@@ -1279,6 +1280,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
}
@SuppressWarnings("WrongCall")
private void onEnd(EndState endState) {
if (endState.isSuccess) {
if (shouldRemoveTask) {
@@ -1290,15 +1292,18 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
pageToSnapTo == (getTaskViewCount() - 1)) {
pageToSnapTo -= 1;
}
removeView(taskView);
removeViewInLayout(taskView);
if (getTaskViewCount() == 0) {
removeView(mClearAllButton);
removeViewInLayout(mClearAllButton);
hideActionsView();
startHome();
} else {
snapToPageImmediately(pageToSnapTo);
}
// Update the layout synchronously so that the position of next view is
// immediately available.
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
}
resetTaskVisuals();
mPendingAnimation = null;
@@ -1548,6 +1553,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateEmptyStateUi(changed);
// Set the pivot points to match the task preview center
@@ -1702,11 +1708,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
BackgroundBlurController blurController = getBackgroundBlurController();
if (blurController != null) {
ObjectAnimator backgroundBlur = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
BACKGROUND_APP.getBackgroundBlurRadius(mActivity));
anim.play(backgroundBlur);
DepthController depthController = getDepthController();
if (depthController != null) {
ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH,
BACKGROUND_APP.getDepth(mActivity));
anim.play(depthAnimator);
}
anim.play(progressAnim);
anim.setDuration(duration).setInterpolator(interpolator);
@@ -2009,7 +2015,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
@Nullable
protected BackgroundBlurController getBackgroundBlurController() {
protected DepthController getDepthController() {
return null;
}
@@ -2029,7 +2035,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
void onEmptyMessageUpdated(boolean isEmpty);
}
private static class PinnedStackAnimationListener<T extends BaseActivity> extends
IPinnedStackAnimationListener.Stub {
private T mActivity;
@@ -247,8 +247,17 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
/** Updates UI based on whether the task is modal. */
public void updateUiForModalTask() {
boolean isOverlayModal = isTaskOverlayModal();
if (getRecentsView() != null) {
getRecentsView().updateUiForModalTask(this, isTaskOverlayModal());
getRecentsView().updateUiForModalTask(this, isOverlayModal);
}
// Hide footers when overlay is modal.
if (isOverlayModal) {
for (FooterWrapper footer : mFooters) {
if (footer != null) {
footer.animateHide();
}
}
}
}
@@ -780,6 +789,22 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
animator.setDuration(100);
animator.start();
}
void animateHide() {
ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
animator.addUpdateListener(anim -> {
mFooterVerticalOffset = anim.getAnimatedFraction();
updateFooterOffset();
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
removeView(mView);
}
});
animator.setDuration(100);
animator.start();
}
}
@Override
@@ -16,9 +16,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layerType="software"
android:background="@color/back_gesture_tutorial_background_color">
<!--The layout is rendered on the software layer to avoid b/136158117-->
<ImageView
android:id="@+id/back_gesture_tutorial_fragment_hand_coaching"
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<color name="back_arrow_color_dark">#99000000</color>
</resources>
@@ -244,7 +244,7 @@ public abstract class BaseQuickstepLauncher extends Launcher
return new StateHandler[] {
getAllAppsController(),
getWorkspace(),
getBackgroundBlurController(),
getDepthController(),
new RecentsViewStateController(this),
new BackButtonAlphaHandler(this)};
}
@@ -16,6 +16,8 @@
package com.android.launcher3;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -29,8 +31,9 @@ import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import static com.android.launcher3.uioverrides.DepthController.DEPTH;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
@@ -58,6 +61,7 @@ import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.NonNull;
@@ -65,17 +69,18 @@ import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.uioverrides.DepthController;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.views.FloatingIconView;
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.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -156,6 +161,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
// Strong refs to runners which are cleared when the launcher activity is destroyed
private WrappedAnimationRunnerImpl mWallpaperOpenRunner;
private WrappedAnimationRunnerImpl mAppLaunchRunner;
private WrappedAnimationRunnerImpl mKeyguardGoingAwayRunner;
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
@Override
@@ -380,18 +386,35 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
alpha.setInterpolator(LINEAR);
launcherAnimator.play(alpha);
mDragLayer.setTranslationY(trans[0]);
ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
transY.setInterpolator(AGGRESSIVE_EASE);
transY.setDuration(CONTENT_TRANSLATION_DURATION);
launcherAnimator.play(transY);
Workspace workspace = mLauncher.getWorkspace();
View currentPage = ((CellLayout) workspace.getChildAt(workspace.getCurrentPage()))
.getShortcutsAndWidgets();
View hotseat = mLauncher.getHotseat();
View qsb = mLauncher.findViewById(R.id.search_container_all_apps);
currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
qsb.setLayerType(View.LAYER_TYPE_HARDWARE, null);
launcherAnimator.play(ObjectAnimator.ofFloat(currentPage, View.TRANSLATION_Y, trans));
launcherAnimator.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, trans));
launcherAnimator.play(ObjectAnimator.ofFloat(qsb, View.TRANSLATION_Y, trans));
mDragLayer.getScrim().hideSysUiScrim(true);
// Pause page indicator animations as they lead to layer trashing.
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
endListener = this::resetContentView;
endListener = () -> {
currentPage.setTranslationY(0);
hotseat.setTranslationY(0);
qsb.setTranslationY(0);
currentPage.setLayerType(View.LAYER_TYPE_NONE, null);
hotseat.setLayerType(View.LAYER_TYPE_NONE, null);
qsb.setLayerType(View.LAYER_TYPE_NONE, null);
mDragLayerAlpha.setValue(1f);
mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
};
}
return new Pair<>(launcherAnimator, endListener);
}
@@ -589,17 +612,17 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
// When launching an app from overview that doesn't map to a task, we still want to just
// blur the wallpaper instead of the launcher surface as well
boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
BACKGROUND_APP.getBackgroundBlurRadius(mLauncher))
DepthController depthController = mLauncher.getDepthController();
ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH,
BACKGROUND_APP.getDepth(mLauncher))
.setDuration(APP_LAUNCH_DURATION);
if (allowBlurringLauncher) {
blurController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
depthController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
appTargets, MODE_OPENING));
backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
blurController.setSurfaceToLauncher(mLauncher.getDragLayer());
depthController.setSurfaceToLauncher(mLauncher.getDragLayer());
}
});
}
@@ -623,6 +646,17 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
false /* startAtFrontOfQueue */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
if (KEYGUARD_ANIMATION.get()) {
mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
definition.addRemoteAnimation(
WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
new RemoteAnimationAdapterCompat(
new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner,
true /* startAtFrontOfQueue */),
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
}
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
}
}
@@ -639,6 +673,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
// definition so we don't have to wait for the system gc
mWallpaperOpenRunner = null;
mAppLaunchRunner = null;
mKeyguardGoingAwayRunner = null;
}
}
@@ -741,62 +776,6 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
return closingAnimator;
}
/**
* Creates an animator that modifies Launcher as a result from
* {@link #createWallpaperOpenRunner}.
*/
private void createLauncherResumeAnimation(AnimatorSet anim) {
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
Pair<AnimatorSet, Runnable> contentAnimator =
getLauncherContentAnimator(false /* isAppOpening */,
new float[] {-mContentTransY, 0});
contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
anim.play(contentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
contentAnimator.second.run();
}
});
} else {
AnimatorSet workspaceAnimator = new AnimatorSet();
mDragLayer.setTranslationY(-mWorkspaceTransY);;
workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
-mWorkspaceTransY, 0));
mDragLayerAlpha.setValue(0);
workspaceAnimator.play(ObjectAnimator.ofFloat(
mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
workspaceAnimator.setDuration(333);
workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
mDragLayer.getScrim().hideSysUiScrim(true);
// Pause page indicator animations as they lead to layer trashing.
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
workspaceAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
resetContentView();
}
});
anim.play(workspaceAnimator);
}
}
private void resetContentView() {
mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
mDragLayerAlpha.setValue(1f);
mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
mDragLayer.setTranslationY(0f);
mDragLayer.getScrim().hideSysUiScrim(false);
}
private boolean hasControlRemoteAppTransitionPermission() {
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
@@ -868,11 +847,12 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
|| mLauncher.isForceInvisible()) {
// Only register the content animation for cancellation when state changes
mLauncher.getStateManager().setCurrentAnimation(anim);
if (mFromUnlock) {
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
Pair<AnimatorSet, Runnable> contentAnimator =
getLauncherContentAnimator(false /* isAppOpening */,
new float[] {mContentTransY, 0});
contentAnimator.first.setStartDelay(0);
new float[] {-mContentTransY, 0});
contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
anim.play(contentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -881,7 +861,12 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
}
});
} else {
createLauncherResumeAnimation(anim);
float velocityDpPerS = DynamicResource.provider(mLauncher)
.getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP,
velocityDpPerS, mLauncher.getResources().getDisplayMetrics());
anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false)
.getAnimators());
}
}
}
@@ -18,7 +18,8 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.util.IntProperty;
import android.os.IBinder;
import android.util.FloatProperty;
import android.view.View;
import com.android.launcher3.Launcher;
@@ -31,22 +32,23 @@ import com.android.launcher3.states.StateAnimationConfig;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SurfaceControlCompat;
import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.WallpaperManagerCompat;
/**
* Controls the blur, for the Launcher surface only.
* Controls blur and wallpaper zoom, for the Launcher surface only.
*/
public class BackgroundBlurController implements LauncherStateManager.StateHandler {
public class DepthController implements LauncherStateManager.StateHandler {
public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
new IntProperty<BackgroundBlurController>("backgroundBlur") {
public static final FloatProperty<DepthController> DEPTH =
new FloatProperty<DepthController>("depth") {
@Override
public void setValue(BackgroundBlurController blurController, int blurRadius) {
blurController.setBackgroundBlurRadius(blurRadius);
public void setValue(DepthController depthController, float depth) {
depthController.setDepth(depth);
}
@Override
public Integer get(BackgroundBlurController blurController) {
return blurController.mBackgroundBlurRadius;
public Float get(DepthController depthController) {
return depthController.mDepth;
}
};
@@ -54,42 +56,50 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl
* A property that updates the background blur within a given range of values (ie. even if the
* animator goes beyond 0..1, the interpolated value will still be bounded).
*/
public static class ClampedBlurProperty extends IntProperty<BackgroundBlurController> {
private final int mMinValue;
private final int mMaxValue;
public static class ClampedDepthProperty extends FloatProperty<DepthController> {
private final float mMinValue;
private final float mMaxValue;
public ClampedBlurProperty(int minValue, int maxValue) {
super(("backgroundBlurClamped"));
public ClampedDepthProperty(float minValue, float maxValue) {
super("depthClamped");
mMinValue = minValue;
mMaxValue = maxValue;
}
@Override
public void setValue(BackgroundBlurController blurController, int blurRadius) {
blurController.setBackgroundBlurRadius(Utilities.boundToRange(blurRadius,
mMinValue, mMaxValue));
public void setValue(DepthController depthController, float depth) {
depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue));
}
@Override
public Integer get(BackgroundBlurController blurController) {
return blurController.mBackgroundBlurRadius;
public Float get(DepthController depthController) {
return depthController.mDepth;
}
}
private final Launcher mLauncher;
/**
* Blur radius when completely zoomed out, in pixels.
*/
private int mMaxBlurRadius;
private WallpaperManagerCompat mWallpaperManager;
private SurfaceControlCompat mSurface;
private int mBackgroundBlurRadius;
/**
* Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
* @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
*/
private float mDepth;
public BackgroundBlurController(Launcher l) {
public DepthController(Launcher l) {
mLauncher = l;
}
/**
* @return the background blur adjustment for folders
*/
public int getFolderBackgroundBlurAdjustment() {
return mLauncher.getResources().getInteger(
R.integer.folder_background_blur_radius_adjustment);
private void ensureDependencies() {
if (mWallpaperManager != null) {
return;
}
mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
mWallpaperManager = new WallpaperManagerCompat(mLauncher);
}
/**
@@ -112,10 +122,10 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl
if (mSurface != surface) {
mSurface = surface;
if (surface != null) {
setBackgroundBlurRadius(mBackgroundBlurRadius);
setDepth(mDepth);
} else {
// If there is no surface, then reset the blur radius
setBackgroundBlurRadius(0);
// If there is no surface, then reset the ratio
setDepth(0f);
}
}
}
@@ -126,9 +136,9 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl
return;
}
int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
setBackgroundBlurRadius(toBackgroundBlurRadius);
float toDepth = toState.getDepth(mLauncher);
if (Float.compare(mDepth, toDepth) != 0) {
setDepth(toDepth);
}
}
@@ -139,22 +149,24 @@ public class BackgroundBlurController implements LauncherStateManager.StateHandl
return;
}
int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
animation.setInt(this, BACKGROUND_BLUR, toBackgroundBlurRadius, LINEAR);
float toDepth = toState.getDepth(mLauncher);
if (Float.compare(mDepth, toDepth) != 0) {
animation.setFloat(this, DEPTH, toDepth, LINEAR);
}
}
private void setBackgroundBlurRadius(int blurRadius) {
// TODO: Do nothing if the shadows are not enabled
// Always update the background blur as it will be reapplied when a surface is next
// available
mBackgroundBlurRadius = blurRadius;
private void setDepth(float depth) {
mDepth = depth;
if (mSurface == null || !mSurface.isValid()) {
return;
}
ensureDependencies();
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
}
new TransactionCompat()
.setBackgroundBlurRadius(mSurface, blurRadius)
.setBackgroundBlurRadius(mSurface, (int) (mDepth * mMaxBlurRadius))
.apply();
}
}
@@ -32,8 +32,11 @@ public class PreviewSurfaceRenderer {
/** Handle a received surface view request. */
public static void render(Context context, Bundle bundle) {
final String gridName = bundle.getString("name");
String gridName = bundle.getString("name");
bundle.remove("name");
if (gridName == null) {
gridName = InvariantDeviceProfile.getCurrentGridName(context);
}
final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName);
MAIN_EXECUTOR.execute(() -> {
@@ -22,7 +22,6 @@ import android.content.Context;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -88,8 +87,8 @@ public class AllAppsState extends LauncherState {
}
@Override
public int getBackgroundBlurRadius(Context context) {
return context.getResources().getInteger(R.integer.allapps_background_blur_radius);
public float getDepth(Context context) {
return 1f;
}
@Override
@@ -33,7 +33,7 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.uioverrides.DepthController;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -81,7 +81,8 @@ public interface BaseActivityInterface<T extends BaseDraggingActivity> {
@Nullable
T getCreatedActivity();
default @Nullable BackgroundBlurController getBackgroundBlurController() {
@Nullable
default DepthController getDepthController() {
return null;
}
@@ -59,6 +59,16 @@ public interface InputConsumer {
return true;
}
/**
* Returns true if the lifecycle of this input consumer is detached from the normal gesture
* down/up flow. If so, it is the responsibility of the input consumer to call back to
* {@link TouchInteractionService#onConsumerInactive(InputConsumer)} after the consumer is
* finished.
*/
default boolean isConsumerDetachedFromGesture() {
return false;
}
/**
* 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
@@ -25,6 +25,7 @@ import android.widget.TextView;
import com.android.launcher3.R;
import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import java.util.Optional;
@@ -79,21 +80,33 @@ abstract class BackGestureTutorialController {
mHandCoachingAnimation.stop();
}
void onGestureDetected() {
hideHandCoachingAnimation();
if (mTutorialStep == TutorialStep.CONFIRM) {
void onGestureAttempted(BackGestureResult result) {
if (mTutorialStep == TutorialStep.CONFIRM
&& (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
|| result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT)) {
mFragment.closeTutorial();
return;
}
if (mTutorialTypeInfo.get().getTutorialType() == TutorialType.RIGHT_EDGE_BACK_NAVIGATION) {
mFragment.changeController(TutorialStep.ENGAGED,
TutorialType.LEFT_EDGE_BACK_NAVIGATION);
if (!mTutorialTypeInfo.isPresent()) {
return;
}
mFragment.changeController(TutorialStep.CONFIRM);
switch (mTutorialTypeInfo.get().getTutorialType()) {
case RIGHT_EDGE_BACK_NAVIGATION:
if (result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
hideHandCoachingAnimation();
mFragment.changeController(
TutorialStep.ENGAGED, TutorialType.LEFT_EDGE_BACK_NAVIGATION);
}
break;
case LEFT_EDGE_BACK_NAVIGATION:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT) {
hideHandCoachingAnimation();
mFragment.changeController(TutorialStep.CONFIRM);
}
break;
}
}
abstract Optional<Integer> getTitleStringId();
@@ -17,21 +17,26 @@ package com.android.quickstep.interaction;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Insets;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import java.net.URISyntaxException;
import java.util.Optional;
/** Shows the Back gesture interactive tutorial. */
public class BackGestureTutorialFragment extends Fragment {
public class BackGestureTutorialFragment extends Fragment implements BackGestureAttemptCallback {
private static final String LOG_TAG = "TutorialFragment";
private static final String KEY_TUTORIAL_STEP = "tutorialStep";
@@ -47,6 +52,7 @@ public class BackGestureTutorialFragment extends Fragment {
private Optional<BackGestureTutorialController> mTutorialController = Optional.empty();
private View mRootView;
private BackGestureTutorialHandAnimation mHandCoachingAnimation;
private EdgeBackGestureHandler mEdgeBackGestureHandler;
public static BackGestureTutorialFragment newInstance(
TutorialStep tutorialStep, TutorialType tutorialType) {
@@ -64,17 +70,25 @@ public class BackGestureTutorialFragment extends Fragment {
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP);
mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
mEdgeBackGestureHandler.registerBackGestureAttemptCallback(this);
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment,
container, /* attachToRoot= */ false);
mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button)
.setOnClickListener(this::onCloseButtonClicked);
mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
return insets;
});
mRootView.setOnTouchListener(mEdgeBackGestureHandler);
mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView);
return mRootView;
@@ -92,6 +106,14 @@ public class BackGestureTutorialFragment extends Fragment {
mHandCoachingAnimation.stop();
}
void onAttachedToWindow() {
mEdgeBackGestureHandler.setIsEnabled(true);
}
void onDetachedFromWindow() {
mEdgeBackGestureHandler.setIsEnabled(false);
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep);
@@ -125,10 +147,9 @@ public class BackGestureTutorialFragment extends Fragment {
this.mTutorialType = tutorialType;
}
void onBackPressed() {
if (mTutorialController.isPresent()) {
mTutorialController.get().onGestureDetected();
}
@Override
public void onBackGestureAttempted(BackGestureResult result) {
mTutorialController.ifPresent(controller -> controller.onGestureAttempted(result));
}
void closeTutorial() {
@@ -0,0 +1,274 @@
/*
* 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 android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import com.android.launcher3.ResourceUtils;
/**
* Utility class to handle edge swipes for back gestures.
*
* Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java.
*/
public class EdgeBackGestureHandler implements DisplayListener, OnTouchListener {
private static final String TAG = "EdgeBackGestureHandler";
private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
"gestures.back_timeout", 250);
private final Context mContext;
private final Point mDisplaySize = new Point();
private final int mDisplayId;
// The edge width where touch down is allowed
private int mEdgeWidth;
// The bottom gesture area height
private int mBottomGestureHeight;
// The slop to distinguish between horizontal and vertical motion
private final float mTouchSlop;
// Duration after which we consider the event as longpress.
private final int mLongPressTimeout;
private final PointF mDownPoint = new PointF();
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
private boolean mIsEnabled;
private int mLeftInset;
private int mRightInset;
private EdgeBackGesturePanel mEdgeBackPanel;
private BackGestureAttemptCallback mGestureCallback;
private final EdgeBackGesturePanel.BackCallback mBackCallback =
new EdgeBackGesturePanel.BackCallback() {
@Override
public void triggerBack() {
if (mGestureCallback != null) {
mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel()
? BackGestureResult.BACK_COMPLETED_FROM_LEFT
: BackGestureResult.BACK_COMPLETED_FROM_RIGHT);
}
}
@Override
public void cancelBack() {
if (mGestureCallback != null) {
mGestureCallback.onBackGestureAttempted(mEdgeBackPanel.getIsLeftPanel()
? BackGestureResult.BACK_CANCELLED_FROM_LEFT
: BackGestureResult.BACK_CANCELLED_FROM_RIGHT);
}
}
};
EdgeBackGestureHandler(Context context) {
final Resources res = context.getResources();
mContext = context;
mDisplayId = context.getDisplay() == null
? Display.DEFAULT_DISPLAY : context.getDisplay().getDisplayId();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
ViewConfiguration.getLongPressTimeout());
mBottomGestureHeight =
ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, res);
mEdgeWidth = ResourceUtils.getNavbarSize("config_backGestureInset", res);
}
void setIsEnabled(boolean isEnabled) {
if (isEnabled == mIsEnabled) {
return;
}
mIsEnabled = isEnabled;
if (mEdgeBackPanel != null) {
mEdgeBackPanel.onDestroy();
mEdgeBackPanel = null;
}
if (!mIsEnabled) {
mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
} else {
updateDisplaySize();
mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
new Handler(Looper.getMainLooper()));
// Add a nav bar panel window.
mEdgeBackPanel = new EdgeBackGesturePanel(mContext);
mEdgeBackPanel.setBackCallback(mBackCallback);
mEdgeBackPanel.setLayoutParams(createLayoutParams());
updateDisplaySize();
}
}
void registerBackGestureAttemptCallback(BackGestureAttemptCallback callback) {
mGestureCallback = callback;
}
private WindowManager.LayoutParams createLayoutParams() {
Resources resources = mContext.getResources();
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
ResourceUtils.getNavbarSize("navigation_edge_panel_width", resources),
ResourceUtils.getNavbarSize("navigation_edge_panel_height", resources),
LayoutParams.TYPE_APPLICATION_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
layoutParams.setTitle(TAG + mDisplayId);
layoutParams.windowAnimations = 0;
layoutParams.setFitInsetsTypes(0 /* types */);
return layoutParams;
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mIsEnabled) {
onMotionEvent(motionEvent);
return true;
}
return false;
}
private boolean isWithinTouchRegion(int x, int y) {
// Disallow if too far from the edge
if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
return false;
}
// Disallow if we are in the bottom gesture area
if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
return false;
}
return true;
}
private void cancelGesture(MotionEvent ev) {
// Send action cancel to reset all the touch events
mAllowGesture = false;
MotionEvent cancelEv = MotionEvent.obtain(ev);
cancelEv.setAction(MotionEvent.ACTION_CANCEL);
mEdgeBackPanel.onMotionEvent(cancelEv);
cancelEv.recycle();
}
private void onMotionEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
boolean isOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
if (mAllowGesture) {
mEdgeBackPanel.setIsLeftPanel(isOnLeftEdge);
mEdgeBackPanel.onMotionEvent(ev);
mDownPoint.set(ev.getX(), ev.getY());
mThresholdCrossed = false;
}
} else if (mAllowGesture) {
if (!mThresholdCrossed) {
if (action == MotionEvent.ACTION_POINTER_DOWN) {
// We do not support multi touch for back gesture
cancelGesture(ev);
return;
} else if (action == MotionEvent.ACTION_MOVE) {
if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
cancelGesture(ev);
return;
}
float dx = Math.abs(ev.getX() - mDownPoint.x);
float dy = Math.abs(ev.getY() - mDownPoint.y);
if (dy > dx && dy > mTouchSlop) {
cancelGesture(ev);
return;
} else if (dx > dy && dx > mTouchSlop) {
mThresholdCrossed = true;
}
}
}
// forward touch
mEdgeBackPanel.onMotionEvent(ev);
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if (!mAllowGesture && mGestureCallback != null) {
mGestureCallback.onBackGestureAttempted(BackGestureResult.BACK_NOT_STARTED);
}
}
}
@Override
public void onDisplayAdded(int displayId) { }
@Override
public void onDisplayRemoved(int displayId) { }
@Override
public void onDisplayChanged(int displayId) {
if (displayId == mDisplayId) {
updateDisplaySize();
}
}
private void updateDisplaySize() {
mContext.getDisplay().getRealSize(mDisplaySize);
if (mEdgeBackPanel != null) {
mEdgeBackPanel.setDisplaySize(mDisplaySize);
}
}
void setInsets(int leftInset, int rightInset) {
mLeftInset = leftInset;
mRightInset = rightInset;
}
enum BackGestureResult {
UNKNOWN,
BACK_COMPLETED_FROM_LEFT,
BACK_COMPLETED_FROM_RIGHT,
BACK_CANCELLED_FROM_LEFT,
BACK_CANCELLED_FROM_RIGHT,
BACK_NOT_STARTED,
}
/** Callback to let the UI react to attempted back gestures. */
interface BackGestureAttemptCallback {
/** Called whenever any touch is completed. */
void onBackGestureAttempted(BackGestureResult result);
}
}
@@ -0,0 +1,701 @@
/*
* 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.interaction;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.os.SystemClock;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import androidx.core.math.MathUtils;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.VibratorWrapper;
/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
public class EdgeBackGesturePanel extends View {
private static final String LOG_TAG = "EdgeBackGesturePanel";
private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
/**
* The time required since the first vibration effect to automatically trigger a click
*/
private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
/**
* The basic translation in dp where the arrow resides
*/
private static final int BASE_TRANSLATION_DP = 32;
/**
* The length of the arrow leg measured from the center to the end
*/
private static final int ARROW_LENGTH_DP = 18;
/**
* The angle measured from the xAxis, where the leg is when the arrow rests
*/
private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56;
/**
* The angle that is added per 1000 px speed to the angle of the leg
*/
private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
/**
* The maximum angle offset allowed due to speed
*/
private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4;
/**
* The thickness of the arrow. Adjusted to match the home handle (approximately)
*/
private static final float ARROW_THICKNESS_DP = 2.5f;
/**
* The amount of rubber banding we do for the vertical translation
*/
private static final int RUBBER_BAND_AMOUNT = 15;
/**
* The interpolator used to rubberband
*/
private static final Interpolator RUBBER_BAND_INTERPOLATOR =
new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
/**
* The amount of rubber banding we do for the translation before base translation
*/
private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
/**
* The interpolator used to rubberband the appearing of the arrow.
*/
private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR =
new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
private final WindowManager mWindowManager;
private BackCallback mBackCallback;
/**
* The paint the arrow is drawn with
*/
private final Paint mPaint = new Paint();
private final float mDensity;
private final float mBaseTranslation;
private final float mArrowLength;
private final float mArrowThickness;
/**
* The minimum delta needed in movement for the arrow to change direction / stop triggering back
*/
private final float mMinDeltaForSwitch;
// The closest to y = 0 that the arrow will be displayed.
private int mMinArrowPosition;
// The amount the arrow is shifted to avoid the finger.
private int mFingerOffset;
private final float mSwipeThreshold;
private final Path mArrowPath = new Path();
private final Point mDisplaySize = new Point();
private final SpringAnimation mAngleAnimation;
private final SpringAnimation mTranslationAnimation;
private final SpringAnimation mVerticalTranslationAnimation;
private final SpringForce mAngleAppearForce;
private final SpringForce mAngleDisappearForce;
private final ValueAnimator mArrowDisappearAnimation;
private final SpringForce mRegularTranslationSpring;
private final SpringForce mTriggerBackSpring;
private VelocityTracker mVelocityTracker;
private int mArrowPaddingEnd;
private WindowManager.LayoutParams mLayoutParams;
/**
* True if the panel is currently on the left of the screen
*/
private boolean mIsLeftPanel;
private float mStartX;
private float mStartY;
private float mCurrentAngle;
/**
* The current translation of the arrow
*/
private float mCurrentTranslation;
/**
* Where the arrow will be in the resting position.
*/
private float mDesiredTranslation;
private boolean mDragSlopPassed;
private boolean mArrowsPointLeft;
private float mMaxTranslation;
private boolean mTriggerBack;
private float mPreviousTouchTranslation;
private float mTotalTouchDelta;
private float mVerticalTranslation;
private float mDesiredVerticalTranslation;
private float mDesiredAngle;
private float mAngleOffset;
private float mDisappearAmount;
private long mVibrationTime;
private int mScreenSize;
private final DynamicAnimation.OnAnimationEndListener mSetGoneEndListener =
new DynamicAnimation.OnAnimationEndListener() {
@Override
public void onAnimationEnd(
DynamicAnimation animation, boolean canceled, float value, float velocity) {
animation.removeEndListener(this);
if (!canceled) {
setVisibility(GONE);
}
}
};
private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_ANGLE =
new FloatPropertyCompat<EdgeBackGesturePanel>("currentAngle") {
@Override
public void setValue(EdgeBackGesturePanel object, float value) {
object.setCurrentAngle(value);
}
@Override
public float getValue(EdgeBackGesturePanel object) {
return object.getCurrentAngle();
}
};
private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_TRANSLATION =
new FloatPropertyCompat<EdgeBackGesturePanel>("currentTranslation") {
@Override
public void setValue(EdgeBackGesturePanel object, float value) {
object.setCurrentTranslation(value);
}
@Override
public float getValue(EdgeBackGesturePanel object) {
return object.getCurrentTranslation();
}
};
private static final FloatPropertyCompat<EdgeBackGesturePanel> CURRENT_VERTICAL_TRANSLATION =
new FloatPropertyCompat<EdgeBackGesturePanel>("verticalTranslation") {
@Override
public void setValue(EdgeBackGesturePanel object, float value) {
object.setVerticalTranslation(value);
}
@Override
public float getValue(EdgeBackGesturePanel object) {
return object.getVerticalTranslation();
}
};
public EdgeBackGesturePanel(Context context) {
super(context);
mWindowManager = context.getSystemService(WindowManager.class);
mDensity = context.getResources().getDisplayMetrics().density;
mBaseTranslation = dp(BASE_TRANSLATION_DP);
mArrowLength = dp(ARROW_LENGTH_DP);
mArrowThickness = dp(ARROW_THICKNESS_DP);
mMinDeltaForSwitch = dp(32);
mPaint.setStrokeWidth(mArrowThickness);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS);
mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mArrowDisappearAnimation.addUpdateListener(animation -> {
mDisappearAmount = (float) animation.getAnimatedValue();
invalidate();
});
mAngleAnimation =
new SpringAnimation(this, CURRENT_ANGLE);
mAngleAppearForce = new SpringForce()
.setStiffness(500)
.setDampingRatio(0.5f);
mAngleDisappearForce = new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
.setFinalPosition(90);
mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
mTranslationAnimation =
new SpringAnimation(this, CURRENT_TRANSLATION);
mRegularTranslationSpring = new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
mTriggerBackSpring = new SpringForce()
.setStiffness(450)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
mTranslationAnimation.setSpring(mRegularTranslationSpring);
mVerticalTranslationAnimation =
new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
mVerticalTranslationAnimation.setSpring(
new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mPaint.setColor(context.getColor(R.color.back_arrow_color_dark));
loadDimens();
updateArrowDirection();
mSwipeThreshold = ResourceUtils.getDimenByName(
"navigation_edge_action_drag_threshold", context.getResources(), 16 /* defaultValue */);
setVisibility(GONE);
}
void onDestroy() {
mWindowManager.removeView(this);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
@SuppressLint("RtlHardcoded")
void setIsLeftPanel(boolean isLeftPanel) {
mIsLeftPanel = isLeftPanel;
mLayoutParams.gravity = mIsLeftPanel
? (Gravity.LEFT | Gravity.TOP)
: (Gravity.RIGHT | Gravity.TOP);
}
boolean getIsLeftPanel() {
return mIsLeftPanel;
}
void setDisplaySize(Point displaySize) {
mDisplaySize.set(displaySize.x, displaySize.y);
mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y);
}
void setBackCallback(BackCallback callback) {
mBackCallback = callback;
}
void setLayoutParams(WindowManager.LayoutParams layoutParams) {
mLayoutParams = layoutParams;
mWindowManager.addView(this, mLayoutParams);
}
private float getCurrentAngle() {
return mCurrentAngle;
}
private float getCurrentTranslation() {
return mCurrentTranslation;
}
void onMotionEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mDragSlopPassed = false;
resetOnDown();
mStartX = event.getX();
mStartY = event.getY();
setVisibility(VISIBLE);
updatePosition(event.getY());
mWindowManager.updateViewLayout(this, mLayoutParams);
break;
case MotionEvent.ACTION_MOVE:
handleMoveEvent(event);
break;
case MotionEvent.ACTION_UP:
if (mTriggerBack) {
triggerBack();
} else {
cancelBack();
}
mVelocityTracker.recycle();
mVelocityTracker = null;
break;
case MotionEvent.ACTION_CANCEL:
cancelBack();
mVelocityTracker.recycle();
mVelocityTracker = null;
break;
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateArrowDirection();
loadDimens();
}
@Override
protected void onDraw(Canvas canvas) {
float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f;
canvas.save();
canvas.translate(
mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition,
(getHeight() * 0.5f) + mVerticalTranslation);
// Let's calculate the position of the end based on the angle
float x = (polarToCartX(mCurrentAngle) * mArrowLength);
float y = (polarToCartY(mCurrentAngle) * mArrowLength);
Path arrowPath = calculatePath(x, y);
canvas.drawPath(arrowPath, mPaint);
canvas.restore();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mMaxTranslation = getWidth() - mArrowPaddingEnd;
}
private void loadDimens() {
Resources res = getResources();
mArrowPaddingEnd = ResourceUtils.getDimenByName("navigation_edge_panel_padding", res, 8);
mMinArrowPosition = ResourceUtils.getDimenByName("navigation_edge_arrow_min_y", res, 64);
mFingerOffset = ResourceUtils.getDimenByName("navigation_edge_finger_offset", res, 48);
}
private void updateArrowDirection() {
// Both panels arrow point the same way
mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
invalidate();
}
private float getStaticArrowWidth() {
return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength;
}
private float polarToCartX(float angleInDegrees) {
return (float) Math.cos(Math.toRadians(angleInDegrees));
}
private float polarToCartY(float angleInDegrees) {
return (float) Math.sin(Math.toRadians(angleInDegrees));
}
private Path calculatePath(float x, float y) {
if (!mArrowsPointLeft) {
x = -x;
}
float extent = lerp(1.0f, 0.75f, mDisappearAmount);
x = x * extent;
y = y * extent;
mArrowPath.reset();
mArrowPath.moveTo(x, y);
mArrowPath.lineTo(0, 0);
mArrowPath.lineTo(x, -y);
return mArrowPath;
}
private static float lerp(float start, float stop, float amount) {
return start + (stop - start) * amount;
}
private void triggerBack() {
if (mBackCallback != null) {
mBackCallback.triggerBack();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.computeCurrentVelocity(1000);
// Only do the extra translation if we're not already flinging
boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
if (isSlow
|| SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
}
// Let's also snap the angle a bit
if (mAngleOffset > -4) {
mAngleOffset = Math.max(-8, mAngleOffset - 8);
updateAngle(true /* animated */);
}
// Finally, after the translation, animate back and disappear the arrow
Runnable translationEnd = () -> {
// let's snap it back
mAngleOffset = Math.max(0, mAngleOffset + 8);
updateAngle(true /* animated */);
mTranslationAnimation.setSpring(mTriggerBackSpring);
// Translate the arrow back a bit to make for a nice transition
setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
.withEndAction(() -> setVisibility(GONE));
mArrowDisappearAnimation.start();
};
if (mTranslationAnimation.isRunning()) {
mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
@Override
public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
float value,
float velocity) {
animation.removeEndListener(this);
if (!canceled) {
translationEnd.run();
}
}
});
} else {
translationEnd.run();
}
}
private void cancelBack() {
if (mBackCallback != null) {
mBackCallback.cancelBack();
}
if (mTranslationAnimation.isRunning()) {
mTranslationAnimation.addEndListener(mSetGoneEndListener);
} else {
setVisibility(GONE);
}
}
private void resetOnDown() {
animate().cancel();
mAngleAnimation.cancel();
mTranslationAnimation.cancel();
mVerticalTranslationAnimation.cancel();
mArrowDisappearAnimation.cancel();
mAngleOffset = 0;
mTranslationAnimation.setSpring(mRegularTranslationSpring);
// Reset the arrow to the side
setTriggerBack(false /* triggerBack */, false /* animated */);
setDesiredTranslation(0, false /* animated */);
setCurrentTranslation(0);
updateAngle(false /* animate */);
mPreviousTouchTranslation = 0;
mTotalTouchDelta = 0;
mVibrationTime = 0;
setDesiredVerticalTransition(0, false /* animated */);
}
private void handleMoveEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
float touchTranslation = Math.abs(x - mStartX);
float yOffset = y - mStartY;
float delta = touchTranslation - mPreviousTouchTranslation;
if (Math.abs(delta) > 0) {
if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
mTotalTouchDelta += delta;
} else {
mTotalTouchDelta = delta;
}
}
mPreviousTouchTranslation = touchTranslation;
// Apply a haptic on drag slop passed
if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
mDragSlopPassed = true;
VibratorWrapper.INSTANCE.get(getContext()).vibrate(VibratorWrapper.EFFECT_CLICK);
mVibrationTime = SystemClock.uptimeMillis();
// Let's show the arrow and animate it in!
mDisappearAmount = 0.0f;
setAlpha(1f);
// And animate it go to back by default!
setTriggerBack(true /* triggerBack */, true /* animated */);
}
// Let's make sure we only go to the baseextend and apply rubberbanding afterwards
if (touchTranslation > mBaseTranslation) {
float diff = touchTranslation - mBaseTranslation;
float progress = MathUtils.clamp(diff / (mScreenSize - mBaseTranslation), 0, 1);
progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
* (mMaxTranslation - mBaseTranslation);
touchTranslation = mBaseTranslation + progress;
} else {
float diff = mBaseTranslation - touchTranslation;
float progress = MathUtils.clamp(diff / mBaseTranslation, 0, 1);
progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress)
* (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR);
touchTranslation = mBaseTranslation - progress;
}
// By default we just assume the current direction is kept
boolean triggerBack = mTriggerBack;
// First lets see if we had continuous motion in one direction for a while
if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
triggerBack = mTotalTouchDelta > 0;
}
// Then, let's see if our velocity tells us to change direction
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
float yVelocity = mVelocityTracker.getYVelocity();
float velocity = (float) Math.hypot(xVelocity, yVelocity);
mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
mAngleOffset *= -1;
}
// Last if the direction in Y is bigger than X * 2 we also abort
if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
triggerBack = false;
}
setTriggerBack(triggerBack, true /* animated */);
if (!mTriggerBack) {
touchTranslation = 0;
} else if (mIsLeftPanel && mArrowsPointLeft
|| (!mIsLeftPanel && !mArrowsPointLeft)) {
// If we're on the left we should move less, because the arrow is facing the other
// direction
touchTranslation -= getStaticArrowWidth();
}
setDesiredTranslation(touchTranslation, true /* animated */);
updateAngle(true /* animated */);
float maxYOffset = getHeight() / 2.0f - mArrowLength;
float progress =
MathUtils.clamp(Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT), 0, 1);
float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
* maxYOffset * Math.signum(yOffset);
setDesiredVerticalTransition(verticalTranslation, true /* animated */);
}
private void updatePosition(float touchY) {
float position = touchY - mFingerOffset;
position = Math.max(position, mMinArrowPosition);
position -= mLayoutParams.height / 2.0f;
mLayoutParams.y = MathUtils.clamp((int) position, 0, mDisplaySize.y);
}
private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
if (mDesiredVerticalTranslation != verticalTranslation) {
mDesiredVerticalTranslation = verticalTranslation;
if (!animated) {
setVerticalTranslation(verticalTranslation);
} else {
mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation);
}
invalidate();
}
}
private void setVerticalTranslation(float verticalTranslation) {
mVerticalTranslation = verticalTranslation;
invalidate();
}
private float getVerticalTranslation() {
return mVerticalTranslation;
}
private void setDesiredTranslation(float desiredTranslation, boolean animated) {
if (mDesiredTranslation != desiredTranslation) {
mDesiredTranslation = desiredTranslation;
if (!animated) {
setCurrentTranslation(desiredTranslation);
} else {
mTranslationAnimation.animateToFinalPosition(desiredTranslation);
}
}
}
private void setCurrentTranslation(float currentTranslation) {
mCurrentTranslation = currentTranslation;
invalidate();
}
private void setTriggerBack(boolean triggerBack, boolean animated) {
if (mTriggerBack != triggerBack) {
mTriggerBack = triggerBack;
mAngleAnimation.cancel();
updateAngle(animated);
// Whenever the trigger back state changes the existing translation animation should be
// cancelled
mTranslationAnimation.cancel();
}
}
private void updateAngle(boolean animated) {
float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
if (newAngle != mDesiredAngle) {
if (!animated) {
setCurrentAngle(newAngle);
} else {
mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce);
mAngleAnimation.animateToFinalPosition(newAngle);
}
mDesiredAngle = newAngle;
}
}
private void setCurrentAngle(float currentAngle) {
mCurrentAngle = currentAngle;
invalidate();
}
private float dp(float dp) {
return mDensity * dp;
}
/** Callback to let the gesture handler react to the detected back gestures. */
interface BackCallback {
/** Indicates that a Back gesture was recognized. */
void triggerBack();
/** Indicates that the gesture was cancelled. */
void cancelBack();
}
}
@@ -30,12 +30,11 @@ import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialSte
import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
import java.util.List;
import java.util.Optional;
/** Shows the gesture interactive sandbox in full screen mode. */
public class GestureSandboxActivity extends FragmentActivity {
Optional<BackGestureTutorialFragment> mFragment = Optional.empty();
private BackGestureTutorialFragment mFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -43,10 +42,10 @@ public class GestureSandboxActivity extends FragmentActivity {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.back_gesture_tutorial_activity);
mFragment = Optional.of(BackGestureTutorialFragment.newInstance(TutorialStep.ENGAGED,
TutorialType.RIGHT_EDGE_BACK_NAVIGATION));
mFragment = BackGestureTutorialFragment.newInstance(
TutorialStep.ENGAGED, TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
getSupportFragmentManager().beginTransaction()
.add(R.id.back_gesture_tutorial_fragment_container, mFragment.get())
.add(R.id.back_gesture_tutorial_fragment_container, mFragment)
.commit();
}
@@ -54,6 +53,13 @@ public class GestureSandboxActivity extends FragmentActivity {
public void onAttachedToWindow() {
super.onAttachedToWindow();
disableSystemGestures();
mFragment.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mFragment.onDetachedFromWindow();
}
@Override
@@ -64,13 +70,6 @@ public class GestureSandboxActivity extends FragmentActivity {
}
}
@Override
public void onBackPressed() {
if (mFragment.isPresent()) {
mFragment.get().onBackPressed();
}
}
private void hideSystemUI() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
@@ -13,39 +13,26 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.views.WorkFooterContainer
<com.android.launcher3.allapps.WorkModeSwitch
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/work_toggle_container"
android:focusable="true"
android:orientation="horizontal"
style="@style/PrimaryMediumText"
android:id="@+id/work_mode_toggle"
android:drawableStart="@drawable/ic_corp"
android:drawablePadding="16dp"
android:drawableTint="?attr/workProfileOverlayTextColor"
android:textColor="?attr/workProfileOverlayTextColor"
android:layout_alignParentBottom="true"
android:ellipsize="end"
android:gravity="start"
android:lines="1"
android:showText="false"
android:textSize="16sp"
android:background="?attr/allAppsScrimColor"
android:text="@string/work_profile_toggle_label"
android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding">
<TextView
style="@style/PrimaryMediumText"
android:id="@+id/work_mode_label"
android:layout_width="0dp"
android:layout_weight="1"
android:drawableStart="@drawable/ic_corp"
android:drawablePadding="16dp"
android:drawableTint="?attr/workProfileOverlayTextColor"
android:textColor="?attr/workProfileOverlayTextColor"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:lines="1"
android:minHeight="24dp"
android:paddingEnd="12dp"
android:text="@string/work_profile_toggle_label"
android:textSize="16sp"/>
<com.android.launcher3.allapps.WorkModeSwitch
android:id="@+id/work_mode_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.android.launcher3.views.WorkFooterContainer>
android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding"
/>
+5
View File
@@ -142,6 +142,7 @@
<item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
<item name="staggered_stiffness" type="dimen" format="float">150</item>
<dimen name="unlock_staggered_velocity_dp_per_s">3dp</dimen>
<!-- Swipe up to home related -->
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
@@ -170,8 +171,12 @@
<item>@dimen/staggered_damping_ratio</item>
<item>@dimen/staggered_stiffness</item>
<item>@dimen/unlock_staggered_velocity_dp_per_s</item>
<item>@dimen/swipe_up_fling_min_visible_change</item>
<item>@dimen/swipe_up_y_overshoot</item>
</array>
<string-array name="live_wallpapers_remove_sysui_scrims">
</string-array>
</resources>
@@ -180,7 +180,10 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
return null;
}
protected static <T extends AbstractFloatingView> T getOpenView(
/**
* Returns a view matching FloatingViewType
*/
public static <T extends AbstractFloatingView> T getOpenView(
ActivityContext activity, @FloatingViewType int type) {
BaseDragLayer dragLayer = activity.getDragLayer();
if (dragLayer == null) return null;
+23 -2
View File
@@ -46,6 +46,7 @@ import android.widget.TextView;
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.IconShape;
@@ -65,7 +66,7 @@ import java.text.NumberFormat;
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
IconLabelDotView {
IconLabelDotView, DraggableView {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -103,7 +104,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private final ActivityContext mActivity;
private Drawable mIcon;
private final boolean mCenterVertically;
private boolean mCenterVertically;
private final int mDisplay;
@@ -701,4 +702,24 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
public int getIconSize() {
return mIconSize;
}
@Override
public int getViewType() {
return DRAGGABLE_ICON;
}
@Override
public void getVisualDragBounds(Rect bounds) {
DeviceProfile grid = mActivity.getDeviceProfile();
BubbleTextView.getIconBounds(this, bounds, grid.iconSizePx);
}
@Override
public void prepareDrawDragView() {
if (getIcon() instanceof FastBitmapDrawable) {
FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
icon.setScale(1f);
}
setForceHideDot(true);
}
}
@@ -155,7 +155,7 @@ public abstract class ButtonDropTarget extends TextView
@Override
public final void onDragEnter(DragObject d) {
if (!d.accessibleDrag && !mTextVisible) {
if (!mAccessibleDrag && !mTextVisible) {
// Show tooltip
hideTooltip();
+90 -86
View File
@@ -53,11 +53,10 @@ import androidx.core.view.ViewCompat;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.RotationMode;
@@ -79,9 +78,6 @@ import java.util.Comparator;
import java.util.Stack;
public class CellLayout extends ViewGroup implements Transposable {
public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
private static final String TAG = "CellLayout";
private static final boolean LOGD = false;
@@ -105,6 +101,11 @@ public class CellLayout extends ViewGroup implements Transposable {
@Thunk final int[] mTmpPoint = new int[2];
@Thunk final int[] mTempLocation = new int[2];
// Used to visualize / debug the Grid of the CellLayout
private static final boolean VISUALIZE_GRID = false;
private Rect mVisualizeGridRect = new Rect();
private Paint mVisualizeGridPaint = new Paint();
private GridOccupancy mOccupied;
private GridOccupancy mTmpOccupied;
@@ -182,7 +183,6 @@ public class CellLayout extends ViewGroup implements Transposable {
private static final Paint sPaint = new Paint();
// Related to accessible drag and drop
private DragAndDropAccessibilityDelegate mTouchHelper;
private boolean mUseTouchHelper = false;
private RotationMode mRotationMode = RotationMode.NORMAL;
@@ -292,26 +292,20 @@ public class CellLayout extends ViewGroup implements Transposable {
addView(mShortcutsAndWidgets);
}
public void enableAccessibleDrag(boolean enable, int dragType) {
mUseTouchHelper = enable;
if (!enable) {
ViewCompat.setAccessibilityDelegate(this, null);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
setOnClickListener(null);
} else {
if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
!(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
mTouchHelper = new WorkspaceAccessibilityHelper(this);
} else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
!(mTouchHelper instanceof FolderAccessibilityHelper)) {
mTouchHelper = new FolderAccessibilityHelper(this);
}
ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
setOnClickListener(mTouchHelper);
}
/**
* Sets or clears a delegate used for accessible drag and drop
*/
public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
setOnClickListener(delegate);
setOnHoverListener(delegate);
ViewCompat.setAccessibilityDelegate(this, delegate);
mUseTouchHelper = delegate != null;
int accessibilityFlag = mUseTouchHelper
? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
setImportantForAccessibility(accessibilityFlag);
getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
// Invalidate the accessibility hierarchy
if (getParent() != null) {
@@ -338,15 +332,6 @@ public class CellLayout extends ViewGroup implements Transposable {
super.setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
}
@Override
public boolean dispatchHoverEvent(MotionEvent event) {
// Always attempt to dispatch hover events to accessibility first.
if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
return true;
}
return super.dispatchHoverEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mUseTouchHelper ||
@@ -483,6 +468,37 @@ public class CellLayout extends ViewGroup implements Transposable {
mFolderLeaveBehind.drawLeaveBehind(canvas);
canvas.restore();
}
if (VISUALIZE_GRID) {
visualizeGrid(canvas);
}
}
protected void visualizeGrid(Canvas canvas) {
mVisualizeGridRect.set(0, 0, mCellWidth, mCellHeight);
mVisualizeGridPaint.setStrokeWidth(4);
for (int i = 0; i < mCountX; i++) {
for (int j = 0; j < mCountY; j++) {
canvas.save();
int transX = i * mCellWidth;
int transY = j * mCellHeight;
canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
mVisualizeGridPaint.setStyle(Paint.Style.FILL);
mVisualizeGridPaint.setColor(Color.argb(80, 255, 100, 100));
canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
mVisualizeGridPaint.setStyle(Paint.Style.STROKE);
mVisualizeGridPaint.setColor(Color.argb(255, 255, 100, 100));
canvas.drawRect(mVisualizeGridRect, mVisualizeGridPaint);
canvas.restore();
}
}
}
@Override
@@ -949,8 +965,8 @@ public class CellLayout extends ViewGroup implements Transposable {
return false;
}
void visualizeDropLocation(View v, DragPreviewProvider outlineProvider, int cellX, int cellY,
int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
void visualizeDropLocation(DraggableView v, DragPreviewProvider outlineProvider, int cellX, int
cellY, int spanX, int spanY, boolean resize, DropTarget.DragObject dragObject) {
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
@@ -960,9 +976,6 @@ public class CellLayout extends ViewGroup implements Transposable {
Bitmap dragOutline = outlineProvider.generatedDragOutline;
if (cellX != oldDragCellX || cellY != oldDragCellY) {
Point dragOffset = dragObject.dragView.getDragVisualizeOffset();
Rect dragRegion = dragObject.dragView.getDragRegion();
mDragCell[0] = cellX;
mDragCell[1] = cellY;
@@ -971,50 +984,27 @@ public class CellLayout extends ViewGroup implements Transposable {
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Rect r = mDragOutlines[mDragOutlineCurrent];
cellToRect(cellX, cellY, spanX, spanY, r);
int left = r.left;
int top = r.top;
int width = dragOutline.getWidth();
int height = dragOutline.getHeight();
if (resize) {
cellToRect(cellX, cellY, spanX, spanY, r);
if (v instanceof LauncherAppWidgetHostView) {
DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
}
} else {
// Find the top left corner of the rect the object will occupy
final int[] topLeft = mTmpPoint;
cellToPoint(cellX, cellY, topLeft);
int left = topLeft[0];
int top = topLeft[1];
if (v != null && dragOffset == null) {
// When drawing the drag outline, it did not account for margin offsets
// added by the view's parent.
MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
left += lp.leftMargin;
top += lp.topMargin;
// Offsets due to the size difference between the View and the dragOutline.
// There is a size difference to account for the outer blur, which may lie
// outside the bounds of the view.
top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
// We center about the x axis
left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
} else {
if (dragOffset != null && dragRegion != null) {
// Center the drag region *horizontally* in the cell and apply a drag
// outline offset
left += dragOffset.x + ((mCellWidth * spanX) - dragRegion.width()) / 2;
int cHeight = getShortcutsAndWidgets().getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
top += dragOffset.y + cellPaddingY;
} else {
// Center the drag outline in the cell
left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
}
}
r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
width = r.width();
height = r.height();
}
if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
int cHeight = getShortcutsAndWidgets().getCellContentHeight();
int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
top += cellPaddingY;
}
r.set(left, top, left + width, top + height);
Utilities.scaleRectAboutCenter(r, mChildScale);
mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
mDragOutlineAnims[mDragOutlineCurrent].animateIn();
@@ -1900,7 +1890,7 @@ public class CellLayout extends ViewGroup implements Transposable {
// This method starts or changes the reorder preview animations
private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
View dragView, int delay, int mode) {
View dragView, int mode) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
@@ -1967,6 +1957,8 @@ public class CellLayout extends ViewGroup implements Transposable {
this.child = child;
this.mode = mode;
// TODO issue!
setInitialAnimationValues(false);
finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
finalDeltaX = initDeltaX;
@@ -2162,6 +2154,8 @@ public class CellLayout extends ViewGroup implements Transposable {
*/
private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
int spanY, View dragView, int[] resultDirection) {
//TODO(adamcohen) b/151776141 use the items visual center for the direction vector
int[] targetDestination = new int[2];
findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
@@ -2272,7 +2266,7 @@ public class CellLayout extends ViewGroup implements Transposable {
setItemPlacementDirty(false);
} else {
beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
ReorderPreviewAnimation.MODE_PREVIEW);
}
mShortcutsAndWidgets.requestLayout();
}
@@ -2326,7 +2320,7 @@ public class CellLayout extends ViewGroup implements Transposable {
if (mode == MODE_SHOW_REORDER_HINT) {
if (finalSolution != null) {
beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
ReorderPreviewAnimation.MODE_HINT);
result[0] = finalSolution.cellX;
result[1] = finalSolution.cellY;
@@ -2366,7 +2360,7 @@ public class CellLayout extends ViewGroup implements Transposable {
setItemPlacementDirty(false);
} else {
beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
ReorderPreviewAnimation.MODE_PREVIEW);
}
}
} else {
@@ -2787,7 +2781,6 @@ public class CellLayout extends ViewGroup implements Transposable {
* Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
*/
public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) return false;
int[] cellPoint = new int[2];
int[] directionVector = new int[]{0, -1};
cellToPoint(0, mCountY, cellPoint);
@@ -2797,12 +2790,23 @@ public class CellLayout extends ViewGroup implements Transposable {
if (commitConfig) {
copySolutionToTempState(configuration, null);
commitTempPlacement();
// undo marking cells occupied since there is actually nothing being placed yet.
mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
}
return true;
}
return false;
}
/**
* returns a copy of cell layout's grid occupancy
*/
public GridOccupancy cloneGridOccupancy() {
GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
mOccupied.copyTo(occupancy);
return occupancy;
}
public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
return mOccupied.isRegionVacant(x, y, spanX, spanY);
}
+5 -3
View File
@@ -23,6 +23,7 @@ import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderNameProvider;
/**
@@ -59,9 +60,6 @@ public interface DropTarget {
/** Where the drag originated */
public DragSource dragSource = null;
/** The object is part of an accessible drag operation */
public boolean accessibleDrag;
/** Indicates that the drag operation was cancelled */
public boolean cancelled = false;
@@ -72,6 +70,10 @@ public interface DropTarget {
public FolderNameProvider folderNameProvider;
/** The source view (ie. icon, widget etc.) that is being dragged and which the
* DragView represents. May be an actual View class or a virtual stand-in */
public DraggableView originalView = null;
public DragObject(Context context) {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
folderNameProvider = FolderNameProvider.newInstance(context);
+1 -1
View File
@@ -79,7 +79,7 @@ public class FolderInfo extends ItemInfo {
* Add an app or shortcut for a specified rank.
*/
public void add(WorkspaceItemInfo item, int rank, boolean animate) {
rank = Utilities.boundToRange(rank, 0, contents.size() + 1);
rank = Utilities.boundToRange(rank, 0, contents.size());
contents.add(rank, item);
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onAdd(item, rank);
@@ -162,9 +162,7 @@ public class InvariantDeviceProfile {
"PreviewContext is passed into this IDP constructor");
}
String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
: null;
String gridName = getCurrentGridName(context);
initGrid(context, gridName);
mConfigMonitor = new ConfigMonitor(context,
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
@@ -188,6 +186,12 @@ public class InvariantDeviceProfile {
initGrid(context, null, new Info(display));
}
public static String getCurrentGridName(Context context) {
return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
: null;
}
/**
* Retrieve system defined or RRO overriden icon shape.
*/
+20 -11
View File
@@ -124,7 +124,7 @@ import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.uioverrides.DepthController;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -326,19 +326,21 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
private boolean mDeferOverlayCallbacks;
private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
private BackgroundBlurController mBackgroundBlurController =
new BackgroundBlurController(this);
private DepthController mDepthController =
new DepthController(this);
private final ViewTreeObserver.OnDrawListener mOnDrawListener =
new ViewTreeObserver.OnDrawListener() {
@Override
public void onDraw() {
getBackgroundBlurController().setSurfaceToLauncher(mDragLayer);
getDepthController().setSurfaceToLauncher(mDragLayer);
mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(
this));
}
};
private long mLastTouchUpTime = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
@@ -954,7 +956,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
NotificationListener.removeNotificationsChangedListener();
getStateManager().moveToRestState();
getBackgroundBlurController().setSurfaceToLauncher(null);
getDepthController().setSurfaceToLauncher(null);
// Workaround for b/78520668, explicitly trim memory once UI is hidden
onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
@@ -1115,7 +1117,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
super.onPause();
mDragController.cancelDrag();
mDragController.resetLastGestureUpTime();
mLastTouchUpTime = -1;
mDropTargetBar.animateToVisibility(false);
if (!mDeferOverlayCallbacks) {
@@ -1838,6 +1840,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
mLastTouchUpTime = System.currentTimeMillis();
}
TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
return super.dispatchTouchEvent(ev);
}
@@ -2465,8 +2470,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
private boolean canRunNewAppsAnimation() {
long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
if (mDragController.isDragging()) {
return false;
} else {
return (System.currentTimeMillis() - mLastTouchUpTime)
> (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
}
}
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
@@ -2708,7 +2717,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
protected StateHandler[] createStateHandlers() {
return new StateHandler[] { getAllAppsController(), getWorkspace(),
getBackgroundBlurController() };
getDepthController() };
}
public TouchController[] createTouchControllers() {
@@ -2745,8 +2754,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
public BackgroundBlurController getBackgroundBlurController() {
return mBackgroundBlurController;
public DepthController getDepthController() {
return mDepthController;
}
public static Launcher getLauncher(Context context) {
+6 -4
View File
@@ -271,11 +271,13 @@ public abstract class LauncherState {
}
/**
* The amount of blur to apply to the background of either the app or Launcher surface in this
* state.
* The amount of blur and wallpaper zoom to apply to the background of either the app
* or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
*
* 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
*/
public int getBackgroundBlurRadius(Context context) {
return 0;
public float getDepth(Context context) {
return 0f;
}
public String getDescription(Launcher launcher) {
@@ -145,38 +145,46 @@ public class ShortcutAndWidgetContainer extends ViewGroup {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (child instanceof LauncherAppWidgetHostView) {
LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
// Scale and center the widget to fit within its cells.
DeviceProfile profile = mActivity.getDeviceProfile();
float scaleX = profile.appWidgetScale.x;
float scaleY = profile.appWidgetScale.y;
lahv.setScaleToFit(Math.min(scaleX, scaleY));
lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
-(lp.height - (lp.height * scaleY)) / 2.0f);
}
int childLeft = lp.x;
int childTop = lp.y;
child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
if (lp.dropped) {
lp.dropped = false;
final int[] cellXY = mTmpCellXY;
getLocationOnScreen(cellXY);
mWallpaperManager.sendWallpaperCommand(getWindowToken(),
WallpaperManager.COMMAND_DROP,
cellXY[0] + childLeft + lp.width / 2,
cellXY[1] + childTop + lp.height / 2, 0, null);
}
layoutChild(child);
}
}
}
/**
* Core logic to layout a child for this ViewGroup.
*/
public void layoutChild(View child) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
if (child instanceof LauncherAppWidgetHostView) {
LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
// Scale and center the widget to fit within its cells.
DeviceProfile profile = mActivity.getDeviceProfile();
float scaleX = profile.appWidgetScale.x;
float scaleY = profile.appWidgetScale.y;
lahv.setScaleToFit(Math.min(scaleX, scaleY));
lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
-(lp.height - (lp.height * scaleY)) / 2.0f);
}
int childLeft = lp.x;
int childTop = lp.y;
child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
if (lp.dropped) {
lp.dropped = false;
final int[] cellXY = mTmpCellXY;
getLocationOnScreen(cellXY);
mWallpaperManager.sendWallpaperCommand(getWindowToken(),
WallpaperManager.COMMAND_DROP,
cellXY[0] + childLeft + lp.width / 2,
cellXY[1] + childTop + lp.height / 2, 0, null);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == ACTION_DOWN && getAlpha() == 0) {
+56 -100
View File
@@ -70,6 +70,7 @@ import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.dragndrop.SpringLoadedDragController;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -81,7 +82,6 @@ import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.WorkspaceTouchListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -192,10 +192,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
final WallpaperOffsetInterpolator mWallpaperOffset;
private boolean mUnlockWallpaperFromDefaultPageOnLayout;
// Variables relating to the creation of user folders by hovering shortcuts over shortcuts
private static final int FOLDER_CREATION_TIMEOUT = 0;
public static final int REORDER_TIMEOUT = 650;
private final Alarm mFolderCreationAlarm = new Alarm();
private final Alarm mReorderAlarm = new Alarm();
private PreviewBackground mFolderCreateBg;
private FolderIcon mDragOverFolderIcon = null;
@@ -567,11 +564,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
addView(newScreen, insertIndex);
mStateTransitionAnimation.applyChildState(
mLauncher.getStateManager().getState(), newScreen, insertIndex);
if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
}
return newScreen;
}
@@ -818,11 +810,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
if (indexOfChild(cl) < currentPage) {
pageShift++;
}
if (isInAccessibleDrag) {
cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
}
removeView(cl);
} else {
// if this is the last screen, convert it to the empty screen
@@ -1444,14 +1431,14 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
child.setVisibility(INVISIBLE);
if (options.isAccessibleDrag) {
mDragController.addDragListener(new AccessibleDragListenerAdapter(
this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
@Override
protected void enableAccessibleDrag(boolean enable) {
super.enableAccessibleDrag(enable);
setEnableForLayout(mLauncher.getHotseat(),enable);
}
});
mDragController.addDragListener(
new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
@Override
protected void enableAccessibleDrag(boolean enable) {
super.enableAccessibleDrag(enable);
setEnableForLayout(mLauncher.getHotseat(), enable);
}
});
}
beginDragShared(child, this, options);
@@ -1465,12 +1452,17 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
+ "View: " + child + " tag: " + child.getTag();
throw new IllegalStateException(msg);
}
beginDragShared(child, source, (ItemInfo) dragObject,
beginDragShared(child, null, source, (ItemInfo) dragObject,
new DragPreviewProvider(child), options);
}
public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
DragPreviewProvider previewProvider, DragOptions dragOptions) {
/**
* Core functionality for beginning a drag operation for an item that will be dropped within
* the workspace
*/
public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
float iconScale = 1f;
if (child instanceof BubbleTextView) {
Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1479,41 +1471,36 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
}
// Clear the pressed state if necessary
child.clearFocus();
child.setPressed(false);
if (child instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) child;
icon.clearPressedBackground();
}
mOutlineProvider = previewProvider;
// The drag bitmap follows the touch point around on the screen
final Bitmap b = previewProvider.createDragBitmap();
int halfPadding = previewProvider.previewPadding / 2;
float scale = previewProvider.getScaleAndPosition(b, mTempXY);
int dragLayerX = mTempXY[0];
int dragLayerY = mTempXY[1];
DeviceProfile grid = mLauncher.getDeviceProfile();
Point dragVisualizeOffset = null;
Rect dragRect = null;
if (child instanceof BubbleTextView) {
dragRect = new Rect();
BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx);
Rect dragRect = new Rect();
if (draggableView == null && child instanceof DraggableView) {
draggableView = (DraggableView) child;
}
if (draggableView != null) {
draggableView.getVisualDragBounds(dragRect);
dragLayerY += dragRect.top;
// Note: The dragRect is used to calculate drag layer offsets, but the
// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
dragVisualizeOffset = new Point(- halfPadding, halfPadding);
} else if (child instanceof FolderIcon) {
int previewSize = grid.folderIconSizePx;
dragVisualizeOffset = new Point(- halfPadding, halfPadding - child.getPaddingTop());
dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
} else if (previewProvider instanceof ShortcutDragPreviewProvider) {
dragVisualizeOffset = new Point(- halfPadding, halfPadding);
}
// Clear the pressed state if necessary
if (child instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) child;
icon.clearPressedBackground();
}
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
@@ -1524,13 +1511,13 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
.showForIcon((BubbleTextView) child);
if (popupContainer != null) {
dragOptions.preDragCondition = popupContainer.createPreDragCondition();
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
}
}
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
scale, dragOptions);
dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
return dv;
}
@@ -1883,12 +1870,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
&& !d.accessibleDrag) {
onCompleteRunnable = new Runnable() {
public void run() {
if (!isPageInTransition()) {
AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
}
&& !options.isAccessibleDrag) {
onCompleteRunnable = () -> {
if (!isPageInTransition()) {
AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
}
};
}
@@ -2088,8 +2073,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
if (mFolderCreateBg != null) {
mFolderCreateBg.animateToRest();
}
mFolderCreationAlarm.setOnAlarmListener(null);
mFolderCreationAlarm.cancelAlarm();
}
private void cleanupAddToFolder() {
@@ -2196,14 +2179,14 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d);
manageFolderFeedback(targetCellDistance, d);
boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
item.spanY, child, mTargetCell);
if (!nearestDropOccupied) {
mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
mDragTargetLayout.visualizeDropLocation(d.originalView, mOutlineProvider,
mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
} else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
&& !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
@@ -2294,10 +2277,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return null;
}
private void manageFolderFeedback(CellLayout targetLayout,
int[] targetCell, float distance, DragObject dragObject) {
private void manageFolderFeedback(float distance, DragObject dragObject) {
if (distance > mMaxDistanceForFolderCreation) {
if (mDragMode != DRAG_MODE_NONE) {
if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER
|| mDragMode == DRAG_MODE_CREATE_FOLDER)) {
setDragMode(DRAG_MODE_NONE);
}
return;
@@ -2306,18 +2289,18 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
ItemInfo info = dragObject.dragInfo;
boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
!mFolderCreationAlarm.alarmPending()) {
if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
FolderCreationAlarmListener listener = new
FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
mFolderCreateBg = new PreviewBackground();
mFolderCreateBg.setup(mLauncher, mLauncher, null,
dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop());
if (!dragObject.accessibleDrag) {
mFolderCreationAlarm.setOnAlarmListener(listener);
mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
} else {
listener.onAlarm(mFolderCreationAlarm);
}
// The full preview background should appear behind the icon
mFolderCreateBg.isClipping = false;
mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]);
mDragTargetLayout.clearDragOutlines();
setDragMode(DRAG_MODE_CREATE_FOLDER);
if (dragObject.stateAnnouncer != null) {
dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
@@ -2330,8 +2313,8 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
mDragOverFolderIcon = ((FolderIcon) dragOverView);
mDragOverFolderIcon.onDragEnter(info);
if (targetLayout != null) {
targetLayout.clearDragOutlines();
if (mDragTargetLayout != null) {
mDragTargetLayout.clearDragOutlines();
}
setDragMode(DRAG_MODE_ADD_TO_FOLDER);
@@ -2350,33 +2333,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
}
class FolderCreationAlarmListener implements OnAlarmListener {
final CellLayout layout;
final int cellX;
final int cellY;
final PreviewBackground bg = new PreviewBackground();
public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
this.layout = layout;
this.cellX = cellX;
this.cellY = cellY;
BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
bg.setup(mLauncher, mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop());
// The full preview background should appear behind the icon
bg.isClipping = false;
}
public void onAlarm(Alarm alarm) {
mFolderCreateBg = bg;
mFolderCreateBg.animateToAccept(layout, cellX, cellY);
layout.clearDragOutlines();
setDragMode(DRAG_MODE_CREATE_FOLDER);
}
}
class ReorderAlarmListener implements OnAlarmListener {
final float[] dragViewCenter;
final int minSpanX, minSpanY, spanX, spanY;
@@ -2413,7 +2369,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
mDragTargetLayout.visualizeDropLocation(child, mOutlineProvider,
mDragTargetLayout.visualizeDropLocation(dragObject.originalView, mOutlineProvider,
mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize, dragObject);
}
}
@@ -16,7 +16,9 @@
package com.android.launcher3.accessibility;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.OnHierarchyChangeListener;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTarget.DragObject;
@@ -24,36 +26,55 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
import java.util.function.Function;
/**
* Utility listener to enable/disable accessibility drag flags for a ViewGroup
* containing CellLayouts
*/
public class AccessibleDragListenerAdapter implements DragListener {
public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener {
private final ViewGroup mViewGroup;
private final int mDragType;
private final Function<CellLayout, DragAndDropAccessibilityDelegate> mDelegateFactory;
/**
* @param parent
* @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or
* {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG}
* @param parent the viewgroup containing all the children
* @param delegateFactory function to create no delegates
*/
public AccessibleDragListenerAdapter(ViewGroup parent, int dragType) {
public AccessibleDragListenerAdapter(ViewGroup parent,
Function<CellLayout, DragAndDropAccessibilityDelegate> delegateFactory) {
mViewGroup = parent;
mDragType = dragType;
mDelegateFactory = delegateFactory;
}
@Override
public void onDragStart(DragObject dragObject, DragOptions options) {
mViewGroup.setOnHierarchyChangeListener(this);
enableAccessibleDrag(true);
}
@Override
public void onDragEnd() {
mViewGroup.setOnHierarchyChangeListener(null);
enableAccessibleDrag(false);
Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this);
}
@Override
public void onChildViewAdded(View parent, View child) {
if (parent == mViewGroup) {
setEnableForLayout((CellLayout) child, true);
}
}
@Override
public void onChildViewRemoved(View parent, View child) {
if (parent == mViewGroup) {
setEnableForLayout((CellLayout) child, false);
}
}
protected void enableAccessibleDrag(boolean enable) {
for (int i = 0; i < mViewGroup.getChildCount(); i++) {
setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable);
@@ -61,6 +82,6 @@ public class AccessibleDragListenerAdapter implements DragListener {
}
protected final void setEnableForLayout(CellLayout layout, boolean enable) {
layout.enableAccessibleDrag(enable, mDragType);
layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null);
}
}
@@ -19,24 +19,26 @@ package com.android.launcher3.accessibility;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnHoverListener;
import android.view.accessibility.AccessibilityEvent;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.customview.widget.ExploreByTouchHelper;
import com.android.launcher3.CellLayout;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import java.util.List;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.customview.widget.ExploreByTouchHelper;
/**
* Helper class to make drag-and-drop in a {@link CellLayout} accessible.
*/
public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper
implements OnClickListener {
implements OnClickListener, OnHoverListener {
protected static final int INVALID_POSITION = -1;
private static final int[] sTempArray = new int[2];
@@ -123,6 +125,11 @@ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHel
node.setFocusable(true);
}
@Override
public boolean onHover(View view, MotionEvent motionEvent) {
return dispatchHoverEvent(motionEvent);
}
protected abstract String getLocationDescriptionForIconDrop(int id);
protected abstract String getConfirmationForIconDrop(int id);
@@ -7,6 +7,7 @@ import static com.android.launcher3.LauncherState.NORMAL;
import android.app.AlertDialog;
import android.appwidget.AppWidgetProviderInfo;
import android.content.DialogInterface;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -400,11 +401,11 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme
Rect pos = new Rect();
mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
mLauncher.getDragController().addDragListener(this);
DragOptions options = new DragOptions();
options.isAccessibleDrag = true;
options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY());
ItemLongClickListener.beginDrag(item, mLauncher, info, options);
}
@@ -38,6 +38,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -62,7 +63,6 @@ import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
import com.android.launcher3.views.WorkFooterContainer;
import java.util.ArrayList;
@@ -91,7 +91,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
private AllAppsPagedView mViewPager;
private FloatingHeaderView mHeader;
private WorkFooterContainer mWorkFooterContainer;
private WorkModeSwitch mWorkModeSwitch;
private SpannableStringBuilder mSearchQueryBuilder = null;
@@ -156,8 +156,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
return mMultiValueAlpha.getProperty(index);
}
public WorkFooterContainer getWorkFooterContainer() {
return mWorkFooterContainer;
public WorkModeSwitch getWorkModeSwitch() {
return mWorkModeSwitch;
}
@@ -195,7 +195,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
}
private void resetWorkProfile() {
mWorkFooterContainer.refresh();
mWorkModeSwitch.refresh();
mAH[AdapterHolder.WORK].setupOverlay();
mAH[AdapterHolder.WORK].applyPadding();
}
@@ -410,9 +410,9 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
} else {
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
mAH[AdapterHolder.WORK].recyclerView = null;
if (mWorkFooterContainer != null) {
((ViewGroup) mWorkFooterContainer.getParent()).removeView(mWorkFooterContainer);
mWorkFooterContainer = null;
if (mWorkModeSwitch != null) {
((ViewGroup) mWorkModeSwitch.getParent()).removeView(mWorkModeSwitch);
mWorkModeSwitch = null;
}
}
setupHeader();
@@ -422,14 +422,11 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
}
private void setupWorkToggle() {
mWorkFooterContainer = (WorkFooterContainer) mLauncher.getLayoutInflater().inflate(
R.layout.work_tab_footer, findViewById(R.id.work_toggle_container));
mWorkFooterContainer.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
this.addView(mWorkFooterContainer);
mWorkFooterContainer.setInsets(mInsets);
mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding());
mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
R.layout.work_mode_switch, this, false);
this.addView(mWorkModeSwitch);
mWorkModeSwitch.setInsets(mInsets);
mWorkModeSwitch.post(() -> mAH[AdapterHolder.WORK].applyPadding());
}
private void replaceRVContainer(boolean showTabs) {
@@ -469,8 +466,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
}
if (mWorkFooterContainer != null) {
mWorkFooterContainer.setWorkTabVisible(pos == AdapterHolder.WORK);
if (mWorkModeSwitch != null) {
mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK);
}
}
@@ -614,6 +611,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
final Rect padding = new Rect();
AllAppsRecyclerView recyclerView;
boolean verticalFadingEdge;
private View mOverlay;
boolean mWorkDisabled;
@@ -648,13 +646,22 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
if (!mIsWork || recyclerView == null) return;
boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
if (mWorkDisabled == workDisabled) return;
recyclerView.setContentDescription(
workDisabled ? mLauncher.getString(R.string.work_apps_paused_title) : null);
View overlayView = getOverlayView();
recyclerView.setItemAnimator(new DefaultItemAnimator());
if (workDisabled) {
overlayView.setAlpha(0);
appsList.updateItemFilter((info, cn) -> false);
recyclerView.addAutoSizedOverlay(
mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null));
recyclerView.addAutoSizedOverlay(overlayView);
overlayView.animate().alpha(1).withEndAction(
() -> recyclerView.setItemAnimator(null)).start();
} else if (mInfoMatcher != null) {
appsList.updateItemFilter(mInfoMatcher);
recyclerView.clearAutoSizedOverlays();
overlayView.animate().alpha(0).withEndAction(() -> {
recyclerView.setItemAnimator(null);
recyclerView.clearAutoSizedOverlays();
}).start();
}
mWorkDisabled = workDisabled;
}
@@ -662,8 +669,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
void applyPadding() {
if (recyclerView != null) {
int bottomOffset =
mWorkFooterContainer != null && mIsWork ? mWorkFooterContainer.getHeight()
: 0;
mWorkModeSwitch != null && mIsWork ? mWorkModeSwitch.getHeight() : 0;
recyclerView.setPadding(padding.left, padding.top, padding.right,
padding.bottom + bottomOffset);
}
@@ -674,5 +680,12 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
mAH[AdapterHolder.MAIN].recyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
&& verticalFadingEdge);
}
private View getOverlayView() {
if (mOverlay == null) {
mOverlay = mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null);
}
return mOverlay;
}
}
}
@@ -18,6 +18,7 @@ package com.android.launcher3.allapps;
import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -55,6 +56,8 @@ public class AllAppsStore {
private int mDeferUpdatesFlags = 0;
private boolean mUpdatePending = false;
private boolean mListenerUpdateInProgress = false;
public AppInfo[] getApps() {
return mApps;
}
@@ -99,10 +102,12 @@ public class AllAppsStore {
mUpdatePending = true;
return;
}
mListenerUpdateInProgress = true;
int count = mUpdateListeners.size();
for (int i = 0; i < count; i++) {
mUpdateListeners.get(i).onAppsUpdated();
}
mListenerUpdateInProgress = false;
}
public void addUpdateListener(OnUpdateListener listener) {
@@ -110,6 +115,9 @@ public class AllAppsStore {
}
public void removeUpdateListener(OnUpdateListener listener) {
if (mListenerUpdateInProgress) {
Log.e("AllAppsStore", "Trying to remove listener during update", new Exception());
}
mUpdateListeners.remove(listener);
}
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* 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.
@@ -15,7 +15,13 @@
*/
package com.android.launcher3.allapps;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Process;
import android.os.UserHandle;
@@ -24,28 +30,45 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Switch;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.UserCache;
import java.lang.ref.WeakReference;
public class WorkModeSwitch extends Switch {
/**
* Work profile toggle switch shown at the bottom of AllApps work tab
*/
public class WorkModeSwitch extends Switch implements Insettable {
private Rect mInsets = new Rect();
protected ObjectAnimator mOpenCloseAnimator;
public WorkModeSwitch(Context context) {
super(context);
init();
}
public WorkModeSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
}
@Override
public void setChecked(boolean checked) {
// No-op, do not change the checked state until broadcast is received.
}
@Override
@@ -55,14 +78,23 @@ public class WorkModeSwitch extends Switch {
private void setCheckedInternal(boolean checked) {
super.setChecked(checked);
setCompoundDrawablesWithIntrinsicBounds(
checked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
}
public void refresh() {
if (!shouldShowWorkSwitch()) return;
UserCache userManager = UserCache.INSTANCE.get(getContext());
setCheckedInternal(!userManager.isAnyProfileQuietModeEnabled());
setEnabled(true);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
this.setVisibility(shouldShowWorkSwitch() ? VISIBLE : GONE);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return ev.getActionMasked() == MotionEvent.ACTION_MOVE || super.onTouchEvent(ev);
@@ -72,6 +104,24 @@ public class WorkModeSwitch extends Switch {
new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
}
@Override
public void setInsets(Rect insets) {
int bottomInset = insets.bottom - mInsets.bottom;
mInsets.set(insets);
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
getPaddingBottom() + bottomInset);
}
/**
* Animates in/out work profile toggle panel based on the tab user is on
*/
public void setWorkTabVisible(boolean workTabVisible) {
if (!shouldShowWorkSwitch()) return;
mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
mOpenCloseAnimator.start();
}
private static final class SetQuietModeEnabledAsyncTask
extends AsyncTask<Void, Void, Boolean> {
@@ -122,4 +172,11 @@ public class WorkModeSwitch extends Switch {
}
}
}
private boolean shouldShowWorkSwitch() {
Launcher launcher = Launcher.getLauncher(getContext());
return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
|| launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
== PackageManager.PERMISSION_GRANTED);
}
}
@@ -77,6 +77,9 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL
}
};
// Progress factor after which an animation is considered almost completed.
private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f;
private final ValueAnimator mAnimationPlayer;
private final long mDuration;
@@ -209,6 +212,16 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL
mAnimationPlayer.start();
}
/**
* Tries to finish the running animation if it is close to completion.
*/
public void forceFinishIfCloseToEnd() {
if (mAnimationPlayer.isRunning()
&& mAnimationPlayer.getAnimatedFraction() > ANIMATION_COMPLETE_THRESHOLD) {
mAnimationPlayer.end();
}
}
/**
* Pauses the currently playing animation.
*/
@@ -62,10 +62,6 @@ public class PendingAnimation implements PropertySetter {
/**
* Utility method to sent an interpolator on an animation and add it to the list
*/
public void add(Animator anim, TimeInterpolator interpolator) {
add(anim, interpolator, SpringProperty.DEFAULT);
}
public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) {
anim.setInterpolator(interpolator);
add(anim, springProperty);
@@ -83,6 +83,9 @@ public final class FeatureFlags {
public static final BooleanFlag UNSTABLE_SPRINGS = getDebugFlag(
"UNSTABLE_SPRINGS", false, "Enable unstable springs for quickstep animations");
public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(
"KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper");
public static final BooleanFlag ADAPTIVE_ICON_WINDOW_ANIM = getDebugFlag(
"ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
@@ -117,12 +120,11 @@ public final class FeatureFlags {
"ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
"Allow Launcher to handle nav bar gestures while Assistant is running over it");
public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
"ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = getDebugFlag(
"HOTSEAT_MIGRATE_NEW_PAGE", false,
"Migrates hotseat to a new workspace page instead of same page");
public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = new DeviceFlag(
"HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder");
public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
"ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
@@ -134,7 +136,7 @@ public final class FeatureFlags {
"ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag(
"USE_SURFACE_VIEW_FOR_GRID_PREVIEW", false, "Use surface view for grid preview");
"USE_SURFACE_VIEW_FOR_GRID_PREVIEW", true, "Use surface view for grid preview");
public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
"ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
@@ -26,7 +26,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
@@ -63,7 +62,6 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS
protected Launcher mLauncher;
private DragController mDragController;
private long mDragStartTime;
public BaseItemDragListener(Rect previewRect, int previewBitmapWidth, int previewViewWidth) {
mPreviewRect = previewRect;
@@ -102,7 +100,7 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS
return false;
}
}
return mDragController.onDragEvent(mDragStartTime, event);
return mDragController.onDragEvent(event);
}
protected boolean onDragStart(DragEvent event) {
@@ -118,7 +116,7 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS
Point downPos = new Point((int) event.getX(), (int) event.getY());
DragOptions options = new DragOptions();
options.systemDndStartPoint = downPos;
options.simulatedDndStartPoint = downPos;
options.preDragCondition = preDragCondition;
// We use drag event position as the screenPos for the preview image. Since mPreviewRect
@@ -128,7 +126,6 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS
// to source window.
createDragHelper().startDrag(new Rect(mPreviewRect),
mPreviewBitmapWidth, mPreviewViewWidth, downPos, this, options);
mDragStartTime = SystemClock.uptimeMillis();
return true;
}
@@ -43,7 +43,6 @@ import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
@@ -61,8 +60,8 @@ public class DragController implements DragDriver.EventListener, TouchController
*/
private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
@Thunk Launcher mLauncher;
private FlingToDeleteHelper mFlingToDeleteHelper;
private final Launcher mLauncher;
private final FlingToDeleteHelper mFlingToDeleteHelper;
// temporaries to avoid gc thrash
private Rect mRectTemp = new Rect();
@@ -77,11 +76,12 @@ public class DragController implements DragDriver.EventListener, TouchController
/** Options controlling the drag behavior. */
private DragOptions mOptions;
/** X coordinate of the down event. */
private int mMotionDownX;
/** Coordinate for motion down event */
private final Point mMotionDown = new Point();
/** Coordinate for last touch event **/
private final Point mLastTouch = new Point();
/** Y coordinate of the down event. */
private int mMotionDownY;
private final Point mTmpPoint = new Point();
private DropTarget.DragObject mDragObject;
@@ -96,12 +96,9 @@ public class DragController implements DragDriver.EventListener, TouchController
private DropTarget mLastDropTarget;
private final int[] mLastTouch = new int[2];
private long mLastTouchUpTime = -1;
private int mLastTouchClassification;
private int mDistanceSinceScroll = 0;
private int mTmpPoint[] = new int[2];
private Rect mDragLayerRect = new Rect();
private boolean mIsInPreDrag;
@@ -140,6 +137,8 @@ public class DragController implements DragDriver.EventListener, TouchController
*
* @param b The bitmap to display as the drag image. It will be re-scaled to the
* enlarged size.
* @param originalView The source view (ie. icon, widget etc.) that is being dragged
* and which the DragView represents
* @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
* @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
* @param source An object representing where the drag originated
@@ -147,7 +146,7 @@ public class DragController implements DragDriver.EventListener, TouchController
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
if (PROFILE_DRAWING_DURING_DRAG) {
@@ -159,13 +158,13 @@ public class DragController implements DragDriver.EventListener, TouchController
AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
mOptions = options;
if (mOptions.systemDndStartPoint != null) {
mLastTouch[0] = mMotionDownX = mOptions.systemDndStartPoint.x;
mLastTouch[1] = mMotionDownY = mOptions.systemDndStartPoint.y;
if (mOptions.simulatedDndStartPoint != null) {
mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x;
mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y;
}
final int registrationX = mMotionDownX - dragLayerX;
final int registrationY = mMotionDownY - dragLayerY;
final int registrationX = mMotionDown.x - dragLayerX;
final int registrationY = mMotionDown.y - dragLayerY;
final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
@@ -173,6 +172,7 @@ public class DragController implements DragDriver.EventListener, TouchController
mLastDropTarget = null;
mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext());
mDragObject.originalView = originalView;
mIsInPreDrag = mOptions.preDragCondition != null
&& !mOptions.preDragCondition.shouldStartDrag(0);
@@ -184,17 +184,13 @@ public class DragController implements DragDriver.EventListener, TouchController
registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
dragView.setItemInfo(dragInfo);
mDragObject.dragComplete = false;
if (mOptions.isAccessibleDrag) {
// For an accessible drag, we assume the view is being dragged from the center.
mDragObject.xOffset = b.getWidth() / 2;
mDragObject.yOffset = b.getHeight() / 2;
mDragObject.accessibleDrag = true;
} else {
mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions);
mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
if (!mOptions.isAccessibleDrag) {
mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
}
mDragObject.dragSource = source;
@@ -210,7 +206,7 @@ public class DragController implements DragDriver.EventListener, TouchController
}
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
dragView.show(mLastTouch[0], mLastTouch[1]);
dragView.show(mLastTouch.x, mLastTouch.y);
mDistanceSinceScroll = 0;
if (!mIsInPreDrag) {
@@ -219,7 +215,7 @@ public class DragController implements DragDriver.EventListener, TouchController
mOptions.preDragCondition.onPreDragStart(mDragObject);
}
handleMoveEvent(mLastTouch[0], mLastTouch[1]);
handleMoveEvent(mLastTouch.x, mLastTouch.y);
mLauncher.getUserEventDispatcher().resetActionDurationMillis();
return dragView;
}
@@ -336,7 +332,7 @@ public class DragController implements DragDriver.EventListener, TouchController
}
}
};
mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration);
mDragObject.dragView.animateTo(mMotionDown.x, mMotionDown.y, onCompleteRunnable, duration);
}
private void callOnDragEnd() {
@@ -365,30 +361,17 @@ public class DragController implements DragDriver.EventListener, TouchController
/**
* Clamps the position to the drag layer bounds.
*/
private int[] getClampedDragLayerPos(float x, float y) {
private Point getClampedDragLayerPos(float x, float y) {
mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
mTmpPoint.x = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
mTmpPoint.y = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
return mTmpPoint;
}
public long getLastGestureUpTime() {
if (mDragDriver != null) {
return System.currentTimeMillis();
} else {
return mLastTouchUpTime;
}
}
public void resetLastGestureUpTime() {
mLastTouchUpTime = -1;
}
@Override
public void onDriverDragMove(float x, float y) {
final int[] dragLayerPos = getClampedDragLayerPos(x, y);
handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);
Point dragLayerPos = getClampedDragLayerPos(x, y);
handleMoveEvent(dragLayerPos.x, dragLayerPos.y);
}
@Override
@@ -422,53 +405,38 @@ public class DragController implements DragDriver.EventListener, TouchController
/**
* Call this from a drag source view.
*/
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (mOptions != null && mOptions.isAccessibleDrag) {
return false;
}
// Update the velocity tracker
mFlingToDeleteHelper.recordMotionEvent(ev);
Point dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
mLastTouch.set(dragLayerPos.x, dragLayerPos.y);
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Remember location of down touch
mMotionDown.set(dragLayerPos.x, dragLayerPos.y);
}
final int action = ev.getAction();
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
final int dragLayerX = dragLayerPos[0];
final int dragLayerY = dragLayerPos[1];
mLastTouch[0] = dragLayerX;
mLastTouch[1] = dragLayerY;
if (ATLEAST_Q) {
mLastTouchClassification = ev.getClassification();
}
switch (action) {
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
mMotionDownX = dragLayerX;
mMotionDownY = dragLayerY;
break;
case MotionEvent.ACTION_UP:
mLastTouchUpTime = System.currentTimeMillis();
break;
}
return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
}
/**
* Call this from a drag source view.
*/
public boolean onDragEvent(long dragStartTime, DragEvent event) {
mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
return mDragDriver != null && mDragDriver.onDragEvent(event);
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
return mDragDriver != null && mDragDriver.onTouchEvent(ev);
}
/**
* Call this from a drag view.
* Call this from a drag source view.
*/
public void onDragViewAnimationEnd() {
if (mDragDriver != null) {
mDragDriver.onDragViewAnimationEnd();
}
public boolean onDragEvent(DragEvent event) {
return mDragDriver != null && mDragDriver.onDragEvent(event);
}
/**
@@ -493,9 +461,8 @@ public class DragController implements DragDriver.EventListener, TouchController
checkTouchMove(dropTarget);
// Check if we are hovering over the scroll areas
mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
mLastTouch[0] = x;
mLastTouch[1] = y;
mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y);
mLastTouch.set(x, y);
int distanceDragged = mDistanceSinceScroll;
if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
@@ -513,7 +480,7 @@ public class DragController implements DragDriver.EventListener, TouchController
public void forceTouchMove() {
int[] dummyCoordinates = mCoordinatesTemp;
DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, dummyCoordinates);
mDragObject.x = dummyCoordinates[0];
mDragObject.y = dummyCoordinates[1];
checkTouchMove(dropTarget);
@@ -536,44 +503,6 @@ public class DragController implements DragDriver.EventListener, TouchController
mLastDropTarget = dropTarget;
}
/**
* Call this from a drag source view.
*/
public boolean onControllerTouchEvent(MotionEvent ev) {
if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
return false;
}
// Update the velocity tracker
mFlingToDeleteHelper.recordMotionEvent(ev);
final int action = ev.getAction();
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
final int dragLayerX = dragLayerPos[0];
final int dragLayerY = dragLayerPos[1];
switch (action) {
case MotionEvent.ACTION_DOWN:
// Remember where the motion event started
mMotionDownX = dragLayerX;
mMotionDownY = dragLayerY;
break;
}
return mDragDriver.onTouchEvent(ev);
}
/**
* Since accessible drag and drop won't cause the same sequence of touch events, we manually
* inject the appropriate state which would have been otherwise initiated via touch events.
*/
public void prepareAccessibleDrag(int x, int y) {
mMotionDownX = x;
mMotionDownY = y;
mLastTouch[0] = x;
mLastTouch[1] = y;
}
/**
* As above, since accessible drag and drop won't cause the same sequence of touch events,
* we manually ensure appropriate drag and drop events get emulated for accessible drag.
@@ -16,19 +16,19 @@
package com.android.launcher3.dragndrop;
import android.content.Context;
import android.util.Log;
import android.os.SystemClock;
import android.view.DragEvent;
import android.view.MotionEvent;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.testing.TestProtocol;
import java.util.function.Consumer;
/**
* Base class for driving a drag/drop operation.
*/
public abstract class DragDriver {
protected final EventListener mEventListener;
protected final Consumer<MotionEvent> mSecondaryEventConsumer;
public interface EventListener {
void onDriverDragMove(float x, float y);
@@ -37,131 +37,175 @@ public abstract class DragDriver {
void onDriverDragCancel();
}
public DragDriver(EventListener eventListener) {
public DragDriver(EventListener eventListener, Consumer<MotionEvent> sec) {
mEventListener = eventListener;
mSecondaryEventConsumer = sec;
}
/**
* Handles ending of the DragView animation.
* Called to handle system touch event
*/
public void onDragViewAnimationEnd() { }
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
break;
}
return true;
return false;
}
public abstract boolean onDragEvent (DragEvent event);
/**
* Called to handle system touch intercept event
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
break;
}
return true;
return false;
}
public static DragDriver create(Context context, DragController dragController,
DragObject dragObject, DragOptions options) {
if (options.systemDndStartPoint != null) {
return new SystemDragDriver(dragController, context, dragObject);
/**
* Called to handle system drag event
*/
public boolean onDragEvent(DragEvent event) {
return false;
}
/**
* Created a driver for handing the actual events
*/
public static DragDriver create(DragController dragController, DragOptions options,
Consumer<MotionEvent> sec) {
if (options.simulatedDndStartPoint != null) {
if (options.isAccessibleDrag) {
return null;
}
return new SystemDragDriver(dragController, sec);
} else {
return new InternalDragDriver(dragController);
return new InternalDragDriver(dragController, sec);
}
}
/**
* Class for driving a system (i.e. framework) drag/drop operation.
*/
static class SystemDragDriver extends DragDriver {
private final long mDragStartTime;
float mLastX = 0;
float mLastY = 0;
SystemDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
super(dragController, sec);
mDragStartTime = SystemClock.uptimeMillis();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
/**
* It creates a temporary {@link MotionEvent} object for secondary consumer
*/
private void simulateSecondaryMotionEvent(DragEvent event) {
final int motionAction;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
motionAction = MotionEvent.ACTION_DOWN;
break;
case DragEvent.ACTION_DRAG_LOCATION:
motionAction = MotionEvent.ACTION_MOVE;
break;
case DragEvent.ACTION_DRAG_ENDED:
motionAction = MotionEvent.ACTION_UP;
break;
default:
return;
}
MotionEvent emulatedEvent = MotionEvent.obtain(mDragStartTime,
SystemClock.uptimeMillis(), motionAction, event.getX(), event.getY(), 0);
mSecondaryEventConsumer.accept(emulatedEvent);
emulatedEvent.recycle();
}
@Override
public boolean onDragEvent(DragEvent event) {
simulateSecondaryMotionEvent(event);
final int action = event.getAction();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
mLastX = event.getX();
mLastY = event.getY();
return true;
case DragEvent.ACTION_DRAG_ENTERED:
return true;
case DragEvent.ACTION_DRAG_LOCATION:
mLastX = event.getX();
mLastY = event.getY();
mEventListener.onDriverDragMove(event.getX(), event.getY());
return true;
case DragEvent.ACTION_DROP:
mLastX = event.getX();
mLastY = event.getY();
mEventListener.onDriverDragMove(event.getX(), event.getY());
mEventListener.onDriverDragEnd(mLastX, mLastY);
return true;
case DragEvent.ACTION_DRAG_EXITED:
mEventListener.onDriverDragExitWindow();
return true;
case DragEvent.ACTION_DRAG_ENDED:
mEventListener.onDriverDragCancel();
return true;
default:
return false;
}
}
}
/**
* Class for driving an internal (i.e. not using framework) drag/drop operation.
*/
static class InternalDragDriver extends DragDriver {
InternalDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
super(dragController, sec);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mSecondaryEventConsumer.accept(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
break;
}
return true;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
mSecondaryEventConsumer.accept(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
break;
}
return true;
}
}
}
/**
* Class for driving a system (i.e. framework) drag/drop operation.
*/
class SystemDragDriver extends DragDriver {
float mLastX = 0;
float mLastY = 0;
SystemDragDriver(DragController dragController, Context context, DragObject dragObject) {
super(dragController);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
@Override
public boolean onDragEvent (DragEvent event) {
final int action = event.getAction();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
mLastX = event.getX();
mLastY = event.getY();
return true;
case DragEvent.ACTION_DRAG_ENTERED:
return true;
case DragEvent.ACTION_DRAG_LOCATION:
mLastX = event.getX();
mLastY = event.getY();
mEventListener.onDriverDragMove(event.getX(), event.getY());
return true;
case DragEvent.ACTION_DROP:
mLastX = event.getX();
mLastY = event.getY();
mEventListener.onDriverDragMove(event.getX(), event.getY());
mEventListener.onDriverDragEnd(mLastX, mLastY);
return true;
case DragEvent.ACTION_DRAG_EXITED:
mEventListener.onDriverDragExitWindow();
return true;
case DragEvent.ACTION_DRAG_ENDED:
mEventListener.onDriverDragCancel();
return true;
default:
return false;
}
}
}
/**
* Class for driving an internal (i.e. not using framework) drag/drop operation.
*/
class InternalDragDriver extends DragDriver {
InternalDragDriver(DragController dragController) {
super(dragController);
}
@Override
public boolean onDragEvent (DragEvent event) { return false; }
}
@@ -41,7 +41,6 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.CellLayout;
@@ -52,7 +51,6 @@ import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
@@ -90,6 +88,8 @@ public class DragLayer extends BaseDragLayer<Launcher> {
private int mTopViewIndex;
private int mChildCountOnLastUpdate = -1;
private Rect mTmpRect = new Rect();
// Related to adjacent page hints
private final ViewGroupFocusHelper mFocusIndicatorHelper;
private final WorkspaceAndHotseatScrim mWorkspaceScrim;
@@ -254,59 +254,46 @@ public class DragLayer extends BaseDragLayer<Launcher> {
public void animateViewIntoPosition(DragView dragView, final View child, int duration,
View anchorView) {
ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
parentChildren.measureChild(child);
parentChildren.layoutChild(child);
Rect r = new Rect();
getViewRectRelativeToSelf(dragView, r);
getViewRectRelativeToSelf(dragView, mTmpRect);
final int fromX = mTmpRect.left;
final int fromY = mTmpRect.top;
float coord[] = new float[2];
float childScale = child.getScaleX();
coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
// Since the child hasn't necessarily been laid out, we force the lp to be updated with
// the correct coordinates (above) and use these to determine the final location
float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
// We need to account for the scale of the child itself, as the above only accounts for
// for the scale in parents.
scale *= childScale;
int toX = Math.round(coord[0]);
int toY = Math.round(coord[1]);
float toScale = scale;
if (child instanceof TextView) {
TextView tv = (TextView) child;
// Account for the source scale of the icon (ie. from AllApps to Workspace, in which
// the workspace may have smaller icon bounds).
toScale = scale / dragView.getIntrinsicIconScaleFactor();
// The child may be scaled (always about the center of the view) so to account for it,
// we have to offset the position by the scaled size. Once we do that, we can center
// the drag view about the scaled child view.
// padding will remain constant (does not scale with size)
toY += tv.getPaddingTop();
toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
if (dragView.getDragVisualizeOffset() != null) {
toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y);
}
if (child instanceof DraggableView) {
DraggableView d = (DraggableView) child;
d.getVisualDragBounds(mTmpRect);
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
} else if (child instanceof FolderIcon) {
// Account for holographic blur padding on the drag view
toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
toY -= scale * dragView.getBlurSizeOutline() / 2;
toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
// Center in the x coordinate about the target's drawable
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
} else {
toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
toX -= (Math.round(scale * (dragView.getMeasuredWidth()
- child.getMeasuredWidth()))) / 2;
// This accounts for the offset of the DragView created by scaling it about its
// center as it animates into place.
float scaleShiftX = dragView.getMeasuredWidth() * (1 - scale) / 2;
float scaleShiftY = dragView.getMeasuredHeight() * (1 - scale) / 2;
toX += scale * (mTmpRect.left - dragView.getBlurSizeOutline() / 2) - scaleShiftX;
toY += scale * (mTmpRect.top - dragView.getBlurSizeOutline() / 2) - scaleShiftY;
}
final int fromX = r.left;
final int fromY = r.top;
child.setVisibility(INVISIBLE);
Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
@@ -559,7 +546,7 @@ public class DragLayer extends BaseDragLayer<Launcher> {
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
mWorkspaceScrim.onInsetsChanged(insets);
mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
mOverviewScrim.onInsetsChanged(insets);
}
@@ -28,8 +28,11 @@ public class DragOptions {
/** Whether or not an accessible drag operation is in progress. */
public boolean isAccessibleDrag = false;
/** Specifies the start location for the system DnD, null when using internal DnD */
public Point systemDndStartPoint = null;
/**
* Specifies the start location for a simulated DnD (like system drag or accessibility drag),
* null when using internal DnD
*/
public Point simulatedDndStartPoint = null;
/** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
public PreDragCondition preDragCondition = null;
@@ -19,8 +19,6 @@ package com.android.launcher3.dragndrop;
import static com.android.launcher3.Utilities.getBadge;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.FloatArrayEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -146,15 +144,6 @@ public class DragView extends View implements LauncherStateManager.StateListener
}
});
mAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (!mAnimationCancelled) {
mDragController.onDragViewAnimationEnd();
}
}
});
mBitmap = bitmap;
setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
@@ -0,0 +1,57 @@
/*
* 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.dragndrop;
import android.graphics.Rect;
/**
* Interface defining methods required for drawing and previewing DragViews, drag previews, and
* related animations
*/
public interface DraggableView {
int DRAGGABLE_ICON = 0;
int DRAGGABLE_WIDGET = 1;
/**
* Static ctr for a simple instance which just returns the view type.
*/
static DraggableView ofType(int type) {
return () -> type;
}
/**
* Certain handling of DragViews depend only on whether this is an Icon Type item or a Widget
* Type item.
*
* @return DRAGGABLE_ICON or DRAGGABLE_WIDGET as appropriate
*/
int getViewType();
/**
* Before rendering as a DragView bitmap, some views need a preparation step.
*/
default void prepareDrawDragView() { }
/**
* If an actual View subclass, this method returns the rectangle (within the View's coordinates)
* of the visual region that should get dragged. This is used to extract exactly that element
* as well as to offset that element as appropriate for various animations
*
* @param bounds Visual bounds in the views coordinates will be written here.
*/
default void getVisualDragBounds(Rect bounds) { }
}
@@ -17,8 +17,6 @@
package com.android.launcher3.dragndrop;
import android.graphics.PointF;
import android.os.SystemClock;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
@@ -55,31 +53,6 @@ public class FlingToDeleteHelper {
mVelocityTracker.addMovement(ev);
}
/**
* Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
* using {@param event} for tracking velocity.
*/
public void recordDragEvent(long dragStartTime, DragEvent event) {
final int motionAction;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
motionAction = MotionEvent.ACTION_DOWN;
break;
case DragEvent.ACTION_DRAG_LOCATION:
motionAction = MotionEvent.ACTION_MOVE;
break;
case DragEvent.ACTION_DRAG_ENDED:
motionAction = MotionEvent.ACTION_UP;
break;
default:
return;
}
MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
motionAction, event.getX(), event.getY(), 0);
recordMotionEvent(emulatedEvent);
emulatedEvent.recycle();
}
public void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -20,10 +20,10 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -33,7 +33,6 @@ import android.util.Log;
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
@@ -50,6 +49,7 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
private final Drawable mBadge;
private final Path mMask;
private final ConstantState mConstantState;
private static final Rect sTmpRect = new Rect();
private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) {
super(bg, fg);
@@ -72,23 +72,14 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
Launcher launcher, int folderId, Point dragViewSize) {
Preconditions.assertNonUiThread();
int margin = launcher.getResources()
.getDimensionPixelSize(R.dimen.blur_size_medium_outline);
// Allocate various bitmaps on the background thread, because why not!
int width = dragViewSize.x - margin;
int height = dragViewSize.y - margin;
if (width <= 0 || height <= 0) {
return null;
}
final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// Create the actual drawable on the UI thread to avoid race conditions with
// FolderIcon draw pass
try {
return MAIN_EXECUTOR.submit(() -> {
FolderIcon icon = launcher.findFolderIcon(folderId);
return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
}).get();
} catch (Exception e) {
Log.e(TAG, "Unable to create folder icon", e);
@@ -96,49 +87,50 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
}
}
/**
* Initializes various bitmaps on the UI thread and returns the final drawable.
*/
private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
Bitmap badgeBitmap, Point dragViewSize) {
Point dragViewSize) {
Preconditions.assertUIThread();
float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2;
Canvas c = new Canvas();
icon.getPreviewBounds(sTmpRect);
PreviewBackground bg = icon.getFolderBackground();
// Initialize badge
c.setBitmap(badgeBitmap);
bg.drawShadow(c);
bg.drawBackgroundStroke(c);
icon.drawDot(c);
// assume square
assert (dragViewSize.x == dragViewSize.y);
final int previewSize = sTmpRect.width();
// Initialize preview
final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction();
final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor);
final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor);
final int margin = (dragViewSize.x - previewSize) / 2;
final float previewShiftX = -sTmpRect.left + margin;
final float previewShiftY = -sTmpRect.top + margin;
final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor;
final float previewShiftX = shiftFactor * previewWidth;
final float previewShiftY = shiftFactor * previewHeight;
Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight,
// Initialize badge, which consists of the outline stroke, shadow and dot; these
// must be rendered above the foreground
Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
(canvas) -> {
int count = canvas.save();
canvas.save();
canvas.translate(previewShiftX, previewShiftY);
icon.getPreviewItemManager().draw(canvas);
canvas.restoreToCount(count);
bg.drawShadow(canvas);
bg.drawBackgroundStroke(canvas);
icon.drawDot(canvas);
canvas.restore();
});
// Initialize mask
Path mask = new Path();
Matrix m = new Matrix();
m.setTranslate(margin, margin);
m.setTranslate(previewShiftX, previewShiftY);
bg.getClipPath().transform(m, mask);
ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin);
ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap,
margin - previewShiftX, margin - previewShiftY);
Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
(canvas) -> {
canvas.save();
canvas.translate(previewShiftX, previewShiftY);
icon.getPreviewItemManager().draw(canvas);
canvas.restore();
});
ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
}
+33 -25
View File
@@ -81,10 +81,12 @@ import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
@@ -271,16 +273,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
mDragController.addDragListener(this);
if (options.isAccessibleDrag) {
mDragController.addDragListener(new AccessibleDragListenerAdapter(
mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) {
@Override
protected void enableAccessibleDrag(boolean enable) {
super.enableAccessibleDrag(enable);
mFooter.setImportantForAccessibility(enable
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
});
mContent, FolderAccessibilityHelper::new) {
@Override
protected void enableAccessibleDrag(boolean enable) {
super.enableAccessibleDrag(enable);
mFooter.setImportantForAccessibility(enable
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
});
}
mLauncher.getWorkspace().beginDragShared(v, this, options);
@@ -324,13 +325,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
public void startEditingFolderName() {
post(() -> {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
if (isEmpty(mFolderName.getText())) {
ofNullable(mInfo)
.map(info -> info.suggestedFolderNames)
.map(folderNames -> (FolderNameInfo[]) folderNames
.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
.ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
}
ofNullable(mInfo)
.map(info -> info.suggestedFolderNames)
.map(folderNames -> (FolderNameInfo[]) folderNames
.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
.ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
}
mFolderName.setHint("");
mIsEditingName = true;
@@ -485,19 +484,24 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
nameInfos[1].getLabel());
if (shouldOpen) {
CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
if (!isEmpty(firstLabel)) {
mFolderName.setHint("");
mFolderName.setText(firstLabel);
// update the primary suggestion if the folder name is empty.
if (isEmpty(mFolderName.getText())) {
CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
if (!isEmpty(firstLabel)) {
mFolderName.setHint("");
mFolderName.setText(firstLabel);
}
}
if (animate) {
animateOpen(mInfo.contents, 0, true);
}
mFolderName.showKeyboard();
mFolderName.displayCompletions(
asList(nameInfos).subList(1, nameInfos.length).stream()
asList(nameInfos).subList(0, nameInfos.length).stream()
.filter(Objects::nonNull)
.map(s -> s.getLabel().toString())
.filter(s -> !s.isEmpty())
.filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString()))
.collect(Collectors.toList()));
}
}
@@ -516,9 +520,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
private void startAnimation(final AnimatorSet a) {
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
mCurrentAnimator.cancel();
}
final Workspace workspace = mLauncher.getWorkspace();
final CellLayout currentCellLayout =
(CellLayout) workspace.getChildAt(workspace.getCurrentPage());
@@ -635,6 +636,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
// dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
mDeleteFolderOnDropCompleted = false;
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
mCurrentAnimator.cancel();
}
AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -738,6 +742,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
private void animateClosed() {
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
mCurrentAnimator.cancel();
}
AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
a.addListener(new AnimatorListenerAdapter() {
@Override
@@ -956,6 +963,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
? mCurrentDragView : mContent.createNewView(info);
ArrayList<View> views = getIconsInReadingOrder();
info.rank = Utilities.boundToRange(info.rank, 0, views.size());
views.add(info.rank, icon);
mContent.arrangeChildren(views);
mItemsInvalidated = true;
@@ -21,7 +21,6 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -47,7 +46,6 @@ import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.uioverrides.BackgroundBlurController;
import com.android.launcher3.util.Themes;
import java.util.List;
@@ -222,14 +220,6 @@ public class FolderAnimationManager {
Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
play(a, z, mIsOpening ? midDuration : 0, midDuration);
BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
int stateBackgroundBlur = mLauncher.getStateManager().getState()
.getBackgroundBlurRadius(mLauncher);
int folderBackgroundBlurAdjustment = blurController.getFolderBackgroundBlurAdjustment();
play(a, ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, mIsOpening
? stateBackgroundBlur + folderBackgroundBlurAdjustment
: stateBackgroundBlur));
// Store clip variables
CellLayout cellLayout = mContent.getCurrentCellLayout();
boolean folderClipChildren = mFolder.getClipChildren();
@@ -63,6 +63,7 @@ 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.dragndrop.DraggableView;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Executors;
@@ -78,7 +79,8 @@ import java.util.function.Predicate;
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
DraggableView {
@Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
@@ -230,6 +232,16 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
mBackground.getBounds(outBounds);
}
@Override
public int getViewType() {
return DRAGGABLE_ICON;
}
@Override
public void getVisualDragBounds(Rect bounds) {
getPreviewBounds(bounds);
}
public float getBackgroundStrokeWidth() {
return mBackground.getStrokeWidth();
}
@@ -525,6 +537,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
invalidate();
}
public boolean getIconVisible() {
return mBackgroundIsVisible;
}
public PreviewBackground getFolderBackground() {
return mBackground;
}
@@ -30,14 +30,12 @@ import android.graphics.drawable.Drawable;
import android.view.View;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import java.nio.ByteBuffer;
@@ -53,7 +51,7 @@ public class DragPreviewProvider {
// The padding added to the drag view during the preview generation.
public final int previewPadding;
protected final int blurSizeOutline;
public final int blurSizeOutline;
private OutlineGeneratorCallback mOutlineGeneratorCallback;
public Bitmap generatedDragOutline;
@@ -66,56 +64,25 @@ public class DragPreviewProvider {
mView = view;
blurSizeOutline =
context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
if (mView instanceof BubbleTextView) {
Drawable d = ((BubbleTextView) mView).getIcon();
Rect bounds = getDrawableBounds(d);
previewPadding = blurSizeOutline - bounds.left - bounds.top;
} else {
previewPadding = blurSizeOutline;
}
previewPadding = blurSizeOutline;
}
/**
* Draws the {@link #mView} into the given {@param destCanvas}.
*/
protected void drawDragView(Canvas destCanvas, float scale) {
destCanvas.save();
int saveCount = destCanvas.save();
destCanvas.scale(scale, scale);
if (mView instanceof BubbleTextView) {
Drawable d = ((BubbleTextView) mView).getIcon();
Rect bounds = getDrawableBounds(d);
destCanvas.translate(blurSizeOutline / 2 - bounds.left,
blurSizeOutline / 2 - bounds.top);
if (d instanceof FastBitmapDrawable) {
((FastBitmapDrawable) d).setScale(1);
}
d.draw(destCanvas);
} else {
final Rect clipRect = mTempRect;
mView.getDrawingRect(clipRect);
boolean textVisible = false;
if (mView instanceof FolderIcon) {
// For FolderIcons the text can bleed into the icon area, and so we need to
// hide the text completely (which can't be achieved by clipping).
if (((FolderIcon) mView).getTextVisible()) {
((FolderIcon) mView).setTextVisible(false);
textVisible = true;
}
}
destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2,
-mView.getScrollY() + blurSizeOutline / 2);
destCanvas.clipRect(clipRect);
if (mView instanceof DraggableView) {
DraggableView dv = (DraggableView) mView;
dv.prepareDrawDragView();
dv.getVisualDragBounds(mTempRect);
destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
blurSizeOutline / 2 - mTempRect.top);
mView.draw(destCanvas);
// Restore text visibility of FolderIcon if necessary
if (textVisible) {
((FolderIcon) mView).setTextVisible(true);
}
}
destCanvas.restore();
destCanvas.restoreToCount(saveCount);
}
/**
@@ -123,28 +90,15 @@ public class DragPreviewProvider {
* Responsibility for the bitmap is transferred to the caller.
*/
public Bitmap createDragBitmap() {
int width = mView.getWidth();
int height = mView.getHeight();
if (mView instanceof BubbleTextView) {
Drawable d = ((BubbleTextView) mView).getIcon();
Rect bounds = getDrawableBounds(d);
width = bounds.width();
height = bounds.height();
} else if (mView instanceof LauncherAppWidgetHostView) {
float scale = ((LauncherAppWidgetHostView) mView).getScaleToFit();
width = (int) (mView.getWidth() * scale);
height = (int) (mView.getHeight() * scale);
if (mView instanceof PendingAppWidgetHostView) {
// Use hardware renderer as the icon for the pending app widget may be a hw bitmap
return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
height + blurSizeOutline, (c) -> drawDragView(c, scale));
} else {
// Use software renderer for widgets as we know that they already work
return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
height + blurSizeOutline, (c) -> drawDragView(c, scale));
}
int width = 0;
int height = 0;
if (mView instanceof DraggableView) {
((DraggableView) mView).getVisualDragBounds(mTempRect);
width = mTempRect.width();
height = mTempRect.height();
} else {
width = mView.getWidth();
height = mView.getHeight();
}
return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
@@ -19,6 +19,7 @@ package com.android.launcher3.graphics;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_USER_PRESENT;
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.ObjectAnimator;
@@ -39,12 +40,14 @@ import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.FloatProperty;
import android.view.View;
import android.view.WindowInsets;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.CellLayout;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.util.Themes;
@@ -81,6 +84,10 @@ public class WorkspaceAndHotseatScrim extends Scrim {
}
};
/**
* Receiver used to get a signal that the user unlocked their device.
* @see KEYGUARD_ANIMATION For proper signal.
*/
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -164,8 +171,10 @@ public class WorkspaceAndHotseatScrim extends Scrim {
mSysUiAnimMultiplier = 0;
reapplySysUiAlphaNoInvalidate();
animateToSysuiMultiplier(1, 600,
mLauncher.getWindow().getTransitionBackgroundFadeDuration());
ObjectAnimator oa = createSysuiMultiplierAnim(1);
oa.setDuration(600);
oa.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration());
oa.start();
mAnimateScrimOnNextDraw = false;
}
@@ -178,26 +187,42 @@ public class WorkspaceAndHotseatScrim extends Scrim {
}
}
public void animateToSysuiMultiplier(float toMultiplier, long duration,
long startDelay) {
ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, toMultiplier);
/**
* @return an ObjectAnimator that controls the fade in/out of the sys ui scrim.
*/
public ObjectAnimator createSysuiMultiplierAnim(float... values) {
ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, values);
anim.setAutoCancel(true);
anim.setDuration(duration);
anim.setStartDelay(startDelay);
anim.start();
return anim;
}
public void onInsetsChanged(Rect insets) {
mDrawTopScrim = mTopScrim != null && insets.top > 0;
mDrawBottomScrim = mBottomMask != null &&
!mLauncher.getDeviceProfile().isVerticalBarLayout();
/**
* Determines whether to draw the top and/or bottom scrim based on new insets.
*/
public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) {
mDrawTopScrim = allowSysuiScrims
&& mTopScrim != null
&& insets.top > 0;
mDrawBottomScrim = allowSysuiScrims
&& mBottomMask != null
&& !mLauncher.getDeviceProfile().isVerticalBarLayout()
&& hasBottomNavButtons();
}
private boolean hasBottomNavButtons() {
if (Utilities.ATLEAST_Q && mLauncher.getRootView() != null
&& mLauncher.getRootView().getRootWindowInsets() != null) {
WindowInsets windowInsets = mLauncher.getRootView().getRootWindowInsets();
return windowInsets.getTappableElementInsets().bottom > 0;
}
return true;
}
@Override
public void onViewAttachedToWindow(View view) {
super.onViewAttachedToWindow(view);
if (mTopScrim != null) {
if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
mRoot.getContext().registerReceiver(mReceiver, filter);
@@ -207,7 +232,7 @@ public class WorkspaceAndHotseatScrim extends Scrim {
@Override
public void onViewDetachedFromWindow(View view) {
super.onViewDetachedFromWindow(view);
if (mTopScrim != null) {
if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
mRoot.getContext().unregisterReceiver(mReceiver);
}
}
@@ -229,14 +254,6 @@ public class WorkspaceAndHotseatScrim extends Scrim {
}
}
public void hideSysUiScrim(boolean hideSysUiScrim) {
mHideSysUiScrim = hideSysUiScrim || (mTopScrim == null);
if (!hideSysUiScrim) {
mAnimateScrimOnNextDraw = true;
}
invalidate();
}
private void setSysUiProgress(float progress) {
if (progress != mSysUiProgress) {
mSysUiProgress = progress;
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.logging;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.view.View;
@@ -27,12 +25,9 @@ import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
import com.android.launcher3.util.InstantAppResolver;
import java.lang.reflect.Field;
@@ -70,93 +65,6 @@ public class LoggerUtils {
return result != null ? result : UNKNOWN;
}
public static String getActionStr(Action action) {
String str = "";
switch (action.type) {
case Action.Type.TOUCH:
str += getFieldName(action.touch, Action.Touch.class);
if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING) {
str += " direction=" + getFieldName(action.dir, Action.Direction.class);
}
break;
case Action.Type.COMMAND:
str += getFieldName(action.command, Action.Command.class);
break;
default: return getFieldName(action.type, Action.Type.class);
}
if (action.touch == Action.Touch.SWIPE || action.touch == Action.Touch.FLING ||
(action.command == Action.Command.BACK && action.dir != Action.Direction.NONE)) {
str += " direction=" + getFieldName(action.dir, Action.Direction.class);
}
return str;
}
public static String getTargetStr(Target t) {
if (t == null) {
return "";
}
String str = "";
switch (t.type) {
case Target.Type.ITEM:
str = getItemStr(t);
break;
case Target.Type.CONTROL:
str = getFieldName(t.controlType, ControlType.class);
break;
case Target.Type.CONTAINER:
str = getFieldName(t.containerType, ContainerType.class);
if (t.containerType == ContainerType.WORKSPACE ||
t.containerType == ContainerType.HOTSEAT ||
t.containerType == NAVBAR) {
str += " id=" + t.pageIndex;
} else if (t.containerType == ContainerType.FOLDER) {
str += " grid(" + t.gridX + "," + t.gridY + ")";
}
break;
default:
str += "UNKNOWN TARGET TYPE";
}
if (t.spanX != 1 || t.spanY != 1) {
str += " span(" + t.spanX + "," + t.spanY + ")";
}
if (t.tipType != TipType.DEFAULT_NONE) {
str += " " + getFieldName(t.tipType, TipType.class);
}
return str;
}
private static String getItemStr(Target t) {
String typeStr = getFieldName(t.itemType, ItemType.class);
if (t.packageNameHash != 0) {
typeStr += ", packageHash=" + t.packageNameHash;
}
if (t.componentHash != 0) {
typeStr += ", componentHash=" + t.componentHash;
}
if (t.intentHash != 0) {
typeStr += ", intentHash=" + t.intentHash;
}
if (t.itemType == ItemType.FOLDER_ICON) {
typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
} else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
&& t.itemType != ItemType.TASK) {
typeStr +=
", isWorkApp=" + t.isWorkApp + ", predictiveRank=" + t.predictedRank + ", grid("
+ t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+ "), pageIdx=" + t.pageIndex;
}
if (t.searchQueryLength != 0) {
typeStr += ", searchQueryLength=" + t.searchQueryLength;
}
if (t.itemType == ItemType.TASK) {
typeStr += ", pageIdx=" + t.pageIndex;
}
return typeStr;
}
public static Target newItemTarget(int itemType) {
Target t = newTarget(Target.Type.ITEM);
t.itemType = itemType;
@@ -424,7 +424,8 @@ public class BgDataModel {
// Now add the new shortcuts to the map.
for (ShortcutInfo shortcut : shortcuts) {
boolean shouldShowInContainer = shortcut.isEnabled()
&& (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
&& (shortcut.isDeclaredInManifest() || shortcut.isDynamic())
&& shortcut.getActivity() != null;
if (shouldShowInContainer) {
ComponentKey targetComponent
= new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
@@ -59,6 +59,7 @@ import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
@@ -662,7 +663,8 @@ public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends Arr
iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
mContainer, sv.getFinalInfo(),
new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
new DragOptions());
@@ -535,7 +535,7 @@ public abstract class AbstractStateChangeTouchController
// case the user started interacting with it before the animation finished.
mLauncher.getStateManager().goToState(targetState, false /* animated */);
}
mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start();
}
private void logReachedState(int logAction, LauncherState targetState) {
@@ -39,7 +39,7 @@ public class VibratorWrapper {
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
new MainThreadInitializedObject<>(VibratorWrapper::new);
private static final VibrationEffect EFFECT_CLICK =
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
/**
@@ -23,7 +23,11 @@ import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.annotation.TargetApi;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -38,11 +42,15 @@ import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.TouchController;
import java.io.PrintWriter;
@@ -98,6 +106,10 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
protected final T mActivity;
private final MultiValueAlpha mMultiValueAlpha;
private final WallpaperManager mWallpaperManager;
private final SimpleBroadcastReceiver mWallpaperChangeReceiver =
new SimpleBroadcastReceiver(this::onWallpaperChanged);
private final String[] mWallpapersWithoutSysuiScrims;
// All the touch controllers for the view
protected TouchController[] mControllers;
@@ -108,10 +120,15 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
private TouchCompleteListener mTouchCompleteListener;
protected boolean mAllowSysuiScrims = true;
public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
super(context, attrs);
mActivity = (T) ActivityContext.lookupContext(context);
mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
mWallpaperManager = context.getSystemService(WallpaperManager.class);
mWallpapersWithoutSysuiScrims = getResources().getStringArray(
R.array.live_wallpapers_remove_sysui_scrims);
}
/**
@@ -517,4 +534,46 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
}
return super.dispatchApplyWindowInsets(insets);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mWallpaperChangeReceiver.register(mActivity, Intent.ACTION_WALLPAPER_CHANGED);
onWallpaperChanged(null);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mActivity.unregisterReceiver(mWallpaperChangeReceiver);
}
private void onWallpaperChanged(Intent unusedBroadcastIntent) {
WallpaperInfo newWallpaperInfo = mWallpaperManager.getWallpaperInfo();
boolean oldAllowSysuiScrims = mAllowSysuiScrims;
mAllowSysuiScrims = computeAllowSysuiScrims(newWallpaperInfo);
if (mAllowSysuiScrims != oldAllowSysuiScrims) {
// Reapply insets so scrim can be removed or re-added if necessary.
setInsets(mInsets);
}
}
/**
* Determines whether we can scrim the status bar and nav bar for the given wallpaper by
* checking against a list of live wallpapers that we don't show the scrims on.
*/
private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) {
if (newWallpaperInfo == null) {
// New wallpaper is static, not live. Thus, blacklist isn't applicable.
return true;
}
ComponentName newWallpaper = newWallpaperInfo.getComponent();
for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) {
if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) {
// New wallpaper is blacklisted from showing a scrim.
return false;
}
}
return true;
}
}
@@ -0,0 +1,349 @@
/*
* 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.Utilities.mapToRange;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.IconShape;
/**
* A view used to draw both layers of an {@link AdaptiveIconDrawable}.
* Supports springing just the foreground layer.
* Supports clipping the icon to/from its icon shape.
*/
@TargetApi(Build.VERSION_CODES.Q)
public class ClipIconView extends View implements ClipPathView {
private static final Rect sTmpRect = new Rect();
// We spring the foreground drawable relative to the icon's movement in the DragLayer.
// We then use these two factor values to scale the movement of the fg within this view.
private static final int FG_TRANS_X_FACTOR = 60;
private static final int FG_TRANS_Y_FACTOR = 75;
private static final FloatPropertyCompat<ClipIconView> mFgTransYProperty =
new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransY") {
@Override
public float getValue(ClipIconView view) {
return view.mFgTransY;
}
@Override
public void setValue(ClipIconView view, float transY) {
view.mFgTransY = transY;
view.invalidate();
}
};
private static final FloatPropertyCompat<ClipIconView> mFgTransXProperty =
new FloatPropertyCompat<ClipIconView>("ClipIconViewFgTransX") {
@Override
public float getValue(ClipIconView view) {
return view.mFgTransX;
}
@Override
public void setValue(ClipIconView view, float transX) {
view.mFgTransX = transX;
view.invalidate();
}
};
private final Launcher mLauncher;
private final int mBlurSizeOutline;
private final boolean mIsRtl;
private @Nullable Drawable mForeground;
private @Nullable Drawable mBackground;
private boolean mIsVerticalBarLayout = false;
private boolean mIsAdaptiveIcon = false;
private ValueAnimator mRevealAnimator;
private final Rect mStartRevealRect = new Rect();
private final Rect mEndRevealRect = new Rect();
private Path mClipPath;
private float mTaskCornerRadius;
private final Rect mOutline = new Rect();
private final Rect mFinalDrawableBounds = new Rect();
private final SpringAnimation mFgSpringY;
private float mFgTransY;
private final SpringAnimation mFgSpringX;
private float mFgTransX;
public ClipIconView(Context context) {
this(context, null);
}
public ClipIconView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ClipIconView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mBlurSizeOutline = getResources().getDimensionPixelSize(
R.dimen.blur_size_medium_outline);
mIsRtl = Utilities.isRtl(getResources());
mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
.setSpring(new SpringForce()
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW));
mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
.setSpring(new SpringForce()
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW));
}
void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
boolean isOpening, float scale, float minSize, LayoutParams parentLp) {
DeviceProfile dp = mLauncher.getDeviceProfile();
float dX = mIsRtl
? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
: rect.left - parentLp.getMarginStart();
float dY = rect.top - parentLp.topMargin;
// shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
float shapeRevealProgress = Utilities.boundToRange(mapToRange(
Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
LINEAR), 0, 1);
if (mIsVerticalBarLayout) {
mOutline.right = (int) (rect.width() / scale);
} else {
mOutline.bottom = (int) (rect.height() / scale);
}
mTaskCornerRadius = cornerRadius / scale;
if (mIsAdaptiveIcon) {
if (!isOpening && progress >= shapeProgressStart) {
if (mRevealAnimator == null) {
mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
mRevealAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRevealAnimator = null;
}
});
mRevealAnimator.start();
// We pause here so we can set the current fraction ourselves.
mRevealAnimator.pause();
}
mRevealAnimator.setCurrentFraction(shapeRevealProgress);
}
float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
/ minSize;
setBackgroundDrawableBounds(drawableScale);
if (isOpening) {
// Center align foreground
int height = mFinalDrawableBounds.height();
int width = mFinalDrawableBounds.width();
int diffY = mIsVerticalBarLayout ? 0
: (int) (((height * drawableScale) - height) / 2);
int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
: 0;
sTmpRect.set(mFinalDrawableBounds);
sTmpRect.offset(diffX, diffY);
mForeground.setBounds(sTmpRect);
} else {
// Spring the foreground relative to the icon's movement within the DragLayer.
int diffX = (int) (dX / dp.availableWidthPx * FG_TRANS_X_FACTOR);
int diffY = (int) (dY / dp.availableHeightPx * FG_TRANS_Y_FACTOR);
mFgSpringX.animateToFinalPosition(diffX);
mFgSpringY.animateToFinalPosition(diffY);
}
}
invalidate();
invalidateOutline();
}
private void setBackgroundDrawableBounds(float scale) {
sTmpRect.set(mFinalDrawableBounds);
Utilities.scaleRectAboutCenter(sTmpRect, scale);
// Since the drawable is at the top of the view, we need to offset to keep it centered.
if (mIsVerticalBarLayout) {
sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
} else {
sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
}
mBackground.setBounds(sTmpRect);
}
protected void endReveal() {
if (mRevealAnimator != null) {
mRevealAnimator.end();
}
}
void setIcon(@Nullable Drawable drawable, int iconOffset, LayoutParams lp, boolean isOpening) {
mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
if (mIsAdaptiveIcon) {
boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
Drawable background = adaptiveIcon.getBackground();
if (background == null) {
background = new ColorDrawable(Color.TRANSPARENT);
}
mBackground = background;
Drawable foreground = adaptiveIcon.getForeground();
if (foreground == null) {
foreground = new ColorDrawable(Color.TRANSPARENT);
}
mForeground = foreground;
final int originalHeight = lp.height;
final int originalWidth = lp.width;
int blurMargin = mBlurSizeOutline / 2;
mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
if (!isFolderIcon) {
mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
}
mForeground.setBounds(mFinalDrawableBounds);
mBackground.setBounds(mFinalDrawableBounds);
mStartRevealRect.set(0, 0, originalWidth, originalHeight);
if (!isFolderIcon) {
Utilities.scaleRectAboutCenter(mStartRevealRect, IconShape.getNormalizationScale());
}
float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
if (mIsVerticalBarLayout) {
lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
} else {
lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
}
int left = mIsRtl
? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
: lp.leftMargin;
layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
float scale = Math.max((float) lp.height / originalHeight,
(float) lp.width / originalWidth);
float bgDrawableStartScale;
if (isOpening) {
bgDrawableStartScale = 1f;
mOutline.set(0, 0, originalWidth, originalHeight);
} else {
bgDrawableStartScale = scale;
mOutline.set(0, 0, lp.width, lp.height);
}
setBackgroundDrawableBounds(bgDrawableStartScale);
mEndRevealRect.set(0, 0, lp.width, lp.height);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(mOutline, mTaskCornerRadius);
}
});
setClipToOutline(true);
} else {
setBackground(drawable);
setClipToOutline(false);
}
invalidate();
invalidateOutline();
}
@Override
public void setClipPath(Path clipPath) {
mClipPath = clipPath;
invalidate();
}
@Override
public void draw(Canvas canvas) {
int count = canvas.save();
if (mClipPath != null) {
canvas.clipPath(mClipPath);
}
super.draw(canvas);
if (mBackground != null) {
mBackground.draw(canvas);
}
if (mForeground != null) {
int count2 = canvas.save();
canvas.translate(mFgTransX, mFgTransY);
mForeground.draw(canvas);
canvas.restoreToCount(count2);
}
canvas.restoreToCount(count);
}
void recycle() {
setBackground(null);
mIsAdaptiveIcon = false;
mForeground = null;
mBackground = null;
mClipPath = null;
mFinalDrawableBounds.setEmpty();
if (mRevealAnimator != null) {
mRevealAnimator.cancel();
}
mRevealAnimator = null;
mTaskCornerRadius = 0;
mOutline.setEmpty();
mFgTransY = 0;
mFgSpringX.cancel();
mFgTransX = 0;
mFgSpringY.cancel();
}
}
@@ -18,8 +18,6 @@ package com.android.launcher3.views;
import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
import static com.android.launcher3.Utilities.getBadge;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -28,17 +26,12 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.CancellationSignal;
@@ -46,19 +39,17 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
@@ -66,8 +57,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -76,8 +65,8 @@ import com.android.launcher3.shortcuts.DeepShortcutView;
* A view that is created to look like another view with the purpose of creating fluid animations.
*/
@TargetApi(Build.VERSION_CODES.Q)
public class FloatingIconView extends View implements
Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener {
public class FloatingIconView extends FrameLayout implements
Animator.AnimatorListener, OnGlobalLayoutListener {
private static final String TAG = FloatingIconView.class.getSimpleName();
@@ -86,81 +75,34 @@ public class FloatingIconView extends View implements
public static final float SHAPE_PROGRESS_DURATION = 0.10f;
private static final int FADE_DURATION_MS = 200;
private static final Rect sTmpRect = new Rect();
private static final RectF sTmpRectF = new RectF();
private static final Object[] sTmpObjArray = new Object[1];
// We spring the foreground drawable relative to the icon's movement in the DragLayer.
// We then use these two factor values to scale the movement of the fg within this view.
private static final int FG_TRANS_X_FACTOR = 60;
private static final int FG_TRANS_Y_FACTOR = 75;
private static final FloatPropertyCompat<FloatingIconView> mFgTransYProperty
= new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransY") {
@Override
public float getValue(FloatingIconView view) {
return view.mFgTransY;
}
@Override
public void setValue(FloatingIconView view, float transY) {
view.mFgTransY = transY;
view.invalidate();
}
};
private static final FloatPropertyCompat<FloatingIconView> mFgTransXProperty
= new FloatPropertyCompat<FloatingIconView>("FloatingViewFgTransX") {
@Override
public float getValue(FloatingIconView view) {
return view.mFgTransX;
}
@Override
public void setValue(FloatingIconView view, float transX) {
view.mFgTransX = transX;
view.invalidate();
}
};
private Runnable mEndRunnable;
private CancellationSignal mLoadIconSignal;
private final Launcher mLauncher;
private final int mBlurSizeOutline;
private final boolean mIsRtl;
private boolean mIsVerticalBarLayout = false;
private boolean mIsAdaptiveIcon = false;
private boolean mIsOpening;
private IconLoadResult mIconLoadResult;
private ClipIconView mClipIconView;
private @Nullable Drawable mBadge;
private @Nullable Drawable mForeground;
private @Nullable Drawable mBackground;
private float mRotation;
private ValueAnimator mRevealAnimator;
private final Rect mStartRevealRect = new Rect();
private final Rect mEndRevealRect = new Rect();
private Path mClipPath;
private float mTaskCornerRadius;
private View mOriginalIcon;
private RectF mPositionOut;
private Runnable mOnTargetChangeRunnable;
private final Rect mOutline = new Rect();
private final Rect mFinalDrawableBounds = new Rect();
private AnimatorSet mFadeAnimatorSet;
private ListenerView mListenerView;
private final SpringAnimation mFgSpringY;
private float mFgTransY;
private final SpringAnimation mFgSpringX;
private float mFgTransX;
public FloatingIconView(Context context) {
this(context, null);
}
@@ -172,19 +114,11 @@ public class FloatingIconView extends View implements
public FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mBlurSizeOutline = getResources().getDimensionPixelSize(
R.dimen.blur_size_medium_outline);
mIsRtl = Utilities.isRtl(getResources());
mListenerView = new ListenerView(context, attrs);
mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
.setSpring(new SpringForce()
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW));
mFgSpringY = new SpringAnimation(this, mFgTransYProperty)
.setSpring(new SpringForce()
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW));
mClipIconView = new ClipIconView(context, attrs);
addView(mClipIconView);
setWillNotDraw(false);
}
@Override
@@ -213,10 +147,12 @@ public class FloatingIconView extends View implements
float cornerRadius, boolean isOpening) {
setAlpha(alpha);
LayoutParams lp = (LayoutParams) getLayoutParams();
InsettableFrameLayout.LayoutParams lp =
(InsettableFrameLayout.LayoutParams) getLayoutParams();
DeviceProfile dp = mLauncher.getDeviceProfile();
float dX = mIsRtl
? rect.left
- (mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width)
? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width)
: rect.left - lp.getMarginStart();
float dY = rect.top - lp.topMargin;
setTranslationX(dX);
@@ -227,69 +163,15 @@ public class FloatingIconView extends View implements
float scaleY = rect.height() / minSize;
float scale = Math.max(1f, Math.min(scaleX, scaleY));
mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
minSize, lp);
setPivotX(0);
setPivotY(0);
setScaleX(scale);
setScaleY(scale);
// shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
float shapeRevealProgress = Utilities.boundToRange(mapToRange(
Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
LINEAR), 0, 1);
if (mIsVerticalBarLayout) {
mOutline.right = (int) (rect.width() / scale);
} else {
mOutline.bottom = (int) (rect.height() / scale);
}
mTaskCornerRadius = cornerRadius / scale;
if (mIsAdaptiveIcon) {
if (!isOpening && progress >= shapeProgressStart) {
if (mRevealAnimator == null) {
mRevealAnimator = (ValueAnimator) IconShape.getShape().createRevealAnimator(
this, mStartRevealRect, mOutline, mTaskCornerRadius, !isOpening);
mRevealAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRevealAnimator = null;
}
});
mRevealAnimator.start();
// We pause here so we can set the current fraction ourselves.
mRevealAnimator.pause();
}
mRevealAnimator.setCurrentFraction(shapeRevealProgress);
}
float drawableScale = (mIsVerticalBarLayout ? mOutline.width() : mOutline.height())
/ minSize;
setBackgroundDrawableBounds(drawableScale);
if (isOpening) {
// Center align foreground
int height = mFinalDrawableBounds.height();
int width = mFinalDrawableBounds.width();
int diffY = mIsVerticalBarLayout ? 0
: (int) (((height * drawableScale) - height) / 2);
int diffX = mIsVerticalBarLayout ? (int) (((width * drawableScale) - width) / 2)
: 0;
sTmpRect.set(mFinalDrawableBounds);
sTmpRect.offset(diffX, diffY);
mForeground.setBounds(sTmpRect);
} else {
// Spring the foreground relative to the icon's movement within the DragLayer.
int diffX = (int) (dX / mLauncher.getDeviceProfile().availableWidthPx
* FG_TRANS_X_FACTOR);
int diffY = (int) (dY / mLauncher.getDeviceProfile().availableHeightPx
* FG_TRANS_Y_FACTOR);
mFgSpringX.animateToFinalPosition(diffX);
mFgSpringY.animateToFinalPosition(diffY);
}
}
invalidate();
invalidateOutline();
}
@Override
@@ -301,9 +183,7 @@ public class FloatingIconView extends View implements
mEndRunnable.run();
} else {
// End runnable also ends the reveal animator, so we manually handle it here.
if (mRevealAnimator != null) {
mRevealAnimator.end();
}
mClipIconView.endReveal();
}
}
@@ -315,23 +195,25 @@ public class FloatingIconView extends View implements
*/
private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) {
float rotation = getLocationBoundsForView(launcher, v, isOpening, positionOut);
final LayoutParams lp = new LayoutParams(
final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
Math.round(positionOut.width()),
Math.round(positionOut.height()));
updatePosition(rotation, positionOut, lp);
setLayoutParams(lp);
mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
}
private void updatePosition(float rotation, RectF position, LayoutParams lp) {
private void updatePosition(float rotation, RectF pos, InsettableFrameLayout.LayoutParams lp) {
mRotation = rotation;
mPositionOut.set(position);
mPositionOut.set(pos);
lp.ignoreInsets = true;
// Position the floating view exactly on top of the original
lp.topMargin = Math.round(position.top);
lp.topMargin = Math.round(pos.top);
if (mIsRtl) {
lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - position.right));
lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right));
} else {
lp.setMarginStart(Math.round(position.left));
lp.setMarginStart(Math.round(pos.left));
}
// Set the properties here already to make sure they are available when running the first
// animation frame.
@@ -412,9 +294,8 @@ public class FloatingIconView extends View implements
drawable = originalView.getBackground();
}
} else {
boolean isFolderIcon = originalView instanceof FolderIcon;
int width = isFolderIcon ? originalView.getWidth() : (int) pos.width();
int height = isFolderIcon ? originalView.getHeight() : (int) pos.height();
int width = (int) pos.width();
int height = (int) pos.height();
if (supportsAdaptiveIcons) {
drawable = getFullDrawable(l, info, width, height, sTmpObjArray);
if (drawable instanceof AdaptiveIconDrawable) {
@@ -451,110 +332,42 @@ public class FloatingIconView extends View implements
/**
* Sets the drawables of the {@param originalView} onto this view.
*
* @param originalView The View that the FloatingIconView will replace.
* @param drawable The drawable of the original view.
* @param badge The badge of the original view.
* @param iconOffset The amount of offset needed to match this view with the original view.
*/
@UiThread
private void setIcon(View originalView, @Nullable Drawable drawable, @Nullable Drawable badge,
int iconOffset) {
private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, int iconOffset) {
final InsettableFrameLayout.LayoutParams lp =
(InsettableFrameLayout.LayoutParams) getLayoutParams();
mBadge = badge;
mIsAdaptiveIcon = drawable instanceof AdaptiveIconDrawable;
if (mIsAdaptiveIcon) {
boolean isFolderIcon = drawable instanceof FolderAdaptiveIcon;
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
Drawable background = adaptiveIcon.getBackground();
if (background == null) {
background = new ColorDrawable(Color.TRANSPARENT);
}
mBackground = background;
Drawable foreground = adaptiveIcon.getForeground();
if (foreground == null) {
foreground = new ColorDrawable(Color.TRANSPARENT);
}
mForeground = foreground;
final LayoutParams lp = (LayoutParams) getLayoutParams();
mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening);
if (drawable instanceof AdaptiveIconDrawable) {
final int originalHeight = lp.height;
final int originalWidth = lp.width;
int blurMargin = mBlurSizeOutline / 2;
mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight);
if (!isFolderIcon) {
mFinalDrawableBounds.inset(iconOffset - blurMargin, iconOffset - blurMargin);
}
mForeground.setBounds(mFinalDrawableBounds);
mBackground.setBounds(mFinalDrawableBounds);
mStartRevealRect.set(0, 0, originalWidth, originalHeight);
if (mBadge != null) {
mBadge.setBounds(mStartRevealRect);
if (!mIsOpening && !isFolderIcon) {
DRAWABLE_ALPHA.set(mBadge, 0);
}
}
if (isFolderIcon) {
((FolderIcon) originalView).getPreviewBounds(sTmpRect);
float bgStroke = ((FolderIcon) originalView).getBackgroundStrokeWidth();
if (mForeground instanceof ShiftedBitmapDrawable) {
ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
}
if (mBadge instanceof ShiftedBitmapDrawable) {
ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mBadge;
sbd.setShiftX(sbd.getShiftX() - sTmpRect.left - bgStroke);
sbd.setShiftY(sbd.getShiftY() - sTmpRect.top - bgStroke);
}
} else {
Utilities.scaleRectAboutCenter(mStartRevealRect,
IconShape.getNormalizationScale());
}
float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
if (mIsVerticalBarLayout) {
lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
} else {
lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
}
setLayoutParams(lp);
int left = mIsRtl
? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
: lp.leftMargin;
layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams();
final int clipViewOgHeight = clipViewLp.height;
final int clipViewOgWidth = clipViewLp.width;
clipViewLp.width = lp.width;
clipViewLp.height = lp.height;
mClipIconView.setLayoutParams(clipViewLp);
float scale = Math.max((float) lp.height / originalHeight,
(float) lp.width / originalWidth);
float bgDrawableStartScale;
if (mIsOpening) {
bgDrawableStartScale = 1f;
mOutline.set(0, 0, originalWidth, originalHeight);
} else {
bgDrawableStartScale = scale;
mOutline.set(0, 0, lp.width, lp.height);
if (mBadge != null) {
mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight);
}
setBackgroundDrawableBounds(bgDrawableStartScale);
mEndRevealRect.set(0, 0, lp.width, lp.height);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(mOutline, mTaskCornerRadius);
}
});
setClipToOutline(true);
} else {
setBackground(drawable);
setClipToOutline(false);
}
invalidate();
invalidateOutline();
}
/**
@@ -571,7 +384,7 @@ public class FloatingIconView extends View implements
synchronized (mIconLoadResult) {
if (mIconLoadResult.isIconLoaded) {
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
hideOriginalView(originalView);
} else {
@@ -580,7 +393,7 @@ public class FloatingIconView extends View implements
return;
}
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
setVisibility(VISIBLE);
@@ -600,23 +413,12 @@ public class FloatingIconView extends View implements
}
}
private void setBackgroundDrawableBounds(float scale) {
sTmpRect.set(mFinalDrawableBounds);
Utilities.scaleRectAboutCenter(sTmpRect, scale);
// Since the drawable is at the top of the view, we need to offset to keep it centered.
if (mIsVerticalBarLayout) {
sTmpRect.offsetTo((int) (mFinalDrawableBounds.left * scale), sTmpRect.top);
} else {
sTmpRect.offsetTo(sTmpRect.left, (int) (mFinalDrawableBounds.top * scale));
}
mBackground.setBounds(sTmpRect);
}
@WorkerThread
@SuppressWarnings("WrongThread")
private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
!(drawable instanceof AdaptiveIconDrawable)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|| !(drawable instanceof AdaptiveIconDrawable)
|| (drawable instanceof FolderAdaptiveIcon)) {
return 0;
}
int blurSizeOutline =
@@ -640,29 +442,11 @@ public class FloatingIconView extends View implements
}
@Override
public void setClipPath(Path clipPath) {
mClipPath = clipPath;
invalidate();
}
@Override
public void draw(Canvas canvas) {
protected void dispatchDraw(Canvas canvas) {
int count = canvas.save();
canvas.rotate(mRotation,
mFinalDrawableBounds.exactCenterX(), mFinalDrawableBounds.exactCenterY());
if (mClipPath != null) {
canvas.clipPath(mClipPath);
}
super.draw(canvas);
if (mBackground != null) {
mBackground.draw(canvas);
}
if (mForeground != null) {
int count2 = canvas.save();
canvas.translate(mFgTransX, mFgTransY);
mForeground.draw(canvas);
canvas.restoreToCount(count2);
}
super.dispatchDraw(canvas);
if (mBadge != null) {
mBadge.draw(canvas);
}
@@ -706,7 +490,8 @@ public class FloatingIconView extends View implements
float rotation = getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening,
sTmpRectF);
if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) {
updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams());
updatePosition(rotation, sTmpRectF,
(InsettableFrameLayout.LayoutParams) getLayoutParams());
if (mOnTargetChangeRunnable != null) {
mOnTargetChangeRunnable.run();
}
@@ -822,12 +607,6 @@ public class FloatingIconView extends View implements
}
});
if (mBadge != null) {
ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
badgeFade.addUpdateListener(valueAnimator -> invalidate());
fade.play(badgeFade);
}
if (originalView instanceof IconLabelDotView) {
IconLabelDotView view = (IconLabelDotView) originalView;
fade.addListener(new AnimatorListenerAdapter() {
@@ -869,21 +648,12 @@ public class FloatingIconView extends View implements
setScaleX(1);
setScaleY(1);
setAlpha(1);
setBackground(null);
if (mLoadIconSignal != null) {
mLoadIconSignal.cancel();
}
mLoadIconSignal = null;
mEndRunnable = null;
mIsAdaptiveIcon = false;
mForeground = null;
mBackground = null;
mClipPath = null;
mFinalDrawableBounds.setEmpty();
if (mRevealAnimator != null) {
mRevealAnimator.cancel();
}
mRevealAnimator = null;
if (mFadeAnimatorSet != null) {
mFadeAnimatorSet.cancel();
}
@@ -892,15 +662,10 @@ public class FloatingIconView extends View implements
mListenerView.setListener(null);
mOriginalIcon = null;
mOnTargetChangeRunnable = null;
mTaskCornerRadius = 0;
mOutline.setEmpty();
mFgTransY = 0;
mFgSpringX.cancel();
mFgTransX = 0;
mFgSpringY.cancel();
mBadge = null;
sTmpObjArray[0] = null;
mIconLoadResult = null;
mClipIconView.recycle();
}
private static class IconLoadResult {
@@ -911,7 +676,7 @@ public class FloatingIconView extends View implements
Runnable onIconLoaded;
boolean isIconLoaded;
public IconLoadResult(ItemInfo itemInfo) {
IconLoadResult(ItemInfo itemInfo) {
this.itemInfo = itemInfo;
}
}
@@ -1,133 +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.views;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.WorkModeSwitch;
import com.android.launcher3.pm.UserCache;
/**
* Container to show work footer in all-apps.
*/
public class WorkFooterContainer extends LinearLayout implements Insettable {
private Rect mInsets = new Rect();
private WorkModeSwitch mWorkModeSwitch;
private TextView mWorkModeLabel;
protected final ObjectAnimator mOpenCloseAnimator;
public WorkFooterContainer(Context context) {
this(context, null, 0);
}
public WorkFooterContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WorkFooterContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
updateTranslation();
this.setVisibility(shouldShowWorkFooter() ? VISIBLE : GONE);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mWorkModeSwitch = findViewById(R.id.work_mode_toggle);
mWorkModeLabel = findViewById(R.id.work_mode_label);
}
@Override
public void offsetTopAndBottom(int offset) {
super.offsetTopAndBottom(offset);
updateTranslation();
}
private void updateTranslation() {
if (getParent() instanceof View) {
View parent = (View) getParent();
int availableBot = parent.getHeight() - parent.getPaddingBottom();
setTranslationY(Math.max(0, availableBot - getBottom()));
}
}
@Override
public void setInsets(Rect insets) {
int bottomInset = insets.bottom - mInsets.bottom;
mInsets.set(insets);
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
getPaddingBottom() + bottomInset);
}
/**
* Animates in/out work profile toggle panel based on the tab user is on
*/
public void setWorkTabVisible(boolean workTabVisible) {
if (!shouldShowWorkFooter()) return;
mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
mOpenCloseAnimator.start();
}
/**
* Refreshes views based on current work profile enabled status
*/
public void refresh() {
if (!shouldShowWorkFooter()) return;
boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
getContext()).isAnyProfileQuietModeEnabled();
mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds(
anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
mWorkModeSwitch.refresh();
}
/**
* Returns work mode switch
*/
public WorkModeSwitch getWorkModeSwitch() {
return mWorkModeSwitch;
}
private boolean shouldShowWorkFooter() {
Launcher launcher = Launcher.getLauncher(getContext());
return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
|| launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
== PackageManager.PERMISSION_GRANTED);
}
}
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
@@ -44,6 +45,7 @@ import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
@@ -52,7 +54,7 @@ import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
implements TouchCompleteListener, View.OnLongClickListener {
implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
// Related to the auto-advancing of widgets
private static final long ADVANCE_INTERVAL = 20000;
@@ -412,4 +414,18 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
}
return false;
}
@Override
public int getViewType() {
return DRAGGABLE_WIDGET;
}
@Override
public void getVisualDragBounds(Rect bounds) {
int x = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
int y = (int) (1 - getScaleToFit()) * getMeasuredWidth() / 2;
int width = (int) getScaleToFit() * getMeasuredWidth();
int height = (int) getScaleToFit() * getMeasuredHeight();
bounds.set(x, y , x + width, y + height);
}
}
@@ -24,12 +24,15 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import com.android.launcher3.dragndrop.DraggableView;
import java.util.ArrayList;
/**
* Extension of AppWidgetHostView with support for controlled keyboard navigation.
*/
public abstract class NavigableAppWidgetHostView extends AppWidgetHostView {
public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
implements DraggableView {
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
@@ -133,4 +136,14 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView {
// The host view's background changes when selected, to indicate the focus is inside.
setSelected(childIsFocused);
}
@Override
public int getViewType() {
return DRAGGABLE_WIDGET;
}
@Override
public void getVisualDragBounds(Rect bounds) {
bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
}
}
@@ -32,6 +32,7 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.icons.LauncherIcons;
@@ -79,6 +80,8 @@ public class PendingItemDragHelper extends DragPreviewProvider {
mEstimatedCellSize = launcher.getWorkspace().estimateItemSize(mAddInfo);
DraggableView draggableView;
if (mAddInfo instanceof PendingAddWidgetInfo) {
PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) mAddInfo;
@@ -110,6 +113,7 @@ public class PendingItemDragHelper extends DragPreviewProvider {
dragOffset = null;
dragRegion = null;
draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET);
} else {
PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
@@ -136,6 +140,7 @@ public class PendingItemDragHelper extends DragPreviewProvider {
dragRegion.top = (mEstimatedCellSize[1]
- iconSize - dp.iconTextSizePx - dp.iconDrawablePaddingPx) / 2;
dragRegion.bottom = dragRegion.top + iconSize;
draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
}
// Since we are not going through the workspace for starting the drag, set drag related
@@ -148,8 +153,8 @@ public class PendingItemDragHelper extends DragPreviewProvider {
+ (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
// Start the drag
launcher.getDragController().startDrag(preview, dragLayerX, dragLayerY, source, mAddInfo,
dragOffset, dragRegion, scale, scale, options);
launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
source, mAddInfo, dragOffset, dragRegion, scale, scale, options);
}
@Override
@@ -17,7 +17,7 @@
package com.android.launcher3.uioverrides;
import android.util.IntProperty;
import android.util.FloatProperty;
import android.view.View;
import com.android.launcher3.Launcher;
@@ -27,26 +27,22 @@ import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.states.StateAnimationConfig;
/**
* Controls the blur, for the Launcher surface only.
* Controls blur and wallpaper zoom, for the Launcher surface only.
*/
public class BackgroundBlurController implements LauncherStateManager.StateHandler {
public class DepthController implements LauncherStateManager.StateHandler {
public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
new IntProperty<BackgroundBlurController>("backgroundBlur") {
public static final FloatProperty<DepthController> DEPTH =
new FloatProperty<DepthController>("depth") {
@Override
public void setValue(BackgroundBlurController blurController, int blurRadius) {}
public void setValue(DepthController depthController, float depth) {}
@Override
public Integer get(BackgroundBlurController blurController) {
return 0;
public Float get(DepthController depthController) {
return 0f;
}
};
public BackgroundBlurController(Launcher l) {}
public int getFolderBackgroundBlurAdjustment() {
return 0;
}
public DepthController(Launcher l) {}
public void setSurfaceToLauncher(View v) {}
@@ -18,7 +18,6 @@ package com.android.launcher3.ui;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -57,6 +56,7 @@ import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.testing.TestProtocol;
@@ -102,6 +102,7 @@ public abstract class AbstractLauncherUiTest {
private static String sDetectedActivityLeak;
private static boolean sActivityLeakReported;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
@@ -213,8 +214,26 @@ public abstract class AbstractLauncherUiTest {
return mDevice;
}
private boolean hasSystemUiObject(String resId) {
return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId));
}
@Before
public void setUp() throws Exception {
Log.d(TAG, "Before disabling battery defender");
mDevice.executeShellCommand("setprop vendor.battery.defender.disable 1");
Log.d(TAG, "Before enabling stay awake");
mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3");
for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) {
Log.d(TAG, "Before unlocking the phone");
mDevice.executeShellCommand("input keyevent 82");
mDevice.waitForIdle();
}
Assert.assertTrue("Keyguard still visible",
mDevice.wait(
Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000));
Log.d(TAG, "Keyguard is not visible");
final String launcherPackageName = mDevice.getLauncherPackageName();
try {
final Context context = InstrumentationRegistry.getContext();
@@ -275,7 +294,8 @@ public abstract class AbstractLauncherUiTest {
protected void resetLoaderState() {
try {
mMainThreadExecutor.execute(
() -> LauncherAppState.getInstance(mTargetContext).getModel().forceReload());
() -> LauncherAppState.getInstance(
mTargetContext).getModel().forceReload());
} catch (Throwable t) {
throw new IllegalArgumentException(t);
}
@@ -289,7 +309,8 @@ public abstract class AbstractLauncherUiTest {
ContentResolver resolver = mTargetContext.getContentResolver();
int screenId = FIRST_SCREEN_ID;
// Update the screen id counter for the provider.
LauncherSettings.Settings.call(resolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
LauncherSettings.Settings.call(resolver,
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
if (screenId > FIRST_SCREEN_ID) {
screenId = FIRST_SCREEN_ID;
@@ -303,7 +324,8 @@ public abstract class AbstractLauncherUiTest {
item.screenId = screenId;
item.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites._ID, item.id);
resolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
writer.getValues(mTargetContext));
resetLoaderState();
// Launch the home activity
@@ -334,7 +356,8 @@ public abstract class AbstractLauncherUiTest {
});
}
// Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
// Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
// expecting
// the results of that gesture because the wait can hide flakeness.
protected void waitForState(String message, Supplier<LauncherState> state) {
waitForLauncherCondition(message,
@@ -347,7 +370,8 @@ public abstract class AbstractLauncherUiTest {
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
protected void waitForLauncherCondition(String message, Function<Launcher, Boolean> condition) {
protected void waitForLauncherCondition(String
message, Function<Launcher, Boolean> condition) {
waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT);
}
@@ -423,7 +447,8 @@ public abstract class AbstractLauncherUiTest {
public Intent blockingGetExtraIntent() throws InterruptedException {
Intent intent = blockingGetIntent();
return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
return intent == null ? null : (Intent) intent.getParcelableExtra(
Intent.EXTRA_INTENT);
}
}
@@ -450,7 +475,8 @@ public abstract class AbstractLauncherUiTest {
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);
intent.addFlags(
Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
}
getInstrumentation().getTargetContext().startActivity(intent);
assertTrue("App didn't start: " + selector,
@@ -487,7 +513,8 @@ public abstract class AbstractLauncherUiTest {
protected boolean isInState(Supplier<LauncherState> state) {
if (!TestHelpers.isInLauncherProcess()) return true;
return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get());
return getFromLauncher(
launcher -> launcher.getStateManager().getState() == state.get());
}
protected int getAllAppsScroll(Launcher launcher) {
@@ -16,6 +16,7 @@
package com.android.launcher3.ui;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -31,9 +32,9 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsPagedView;
import com.android.launcher3.allapps.WorkModeSwitch;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.views.WorkEduView;
import com.android.launcher3.views.WorkFooterContainer;
import org.junit.After;
import org.junit.Before;
@@ -87,7 +88,7 @@ public class WorkTabTest extends AbstractLauncherUiTest {
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
getOnceNotNull("Apps view did not bind",
launcher -> launcher.getAppsView().getWorkFooterContainer(), 60000);
launcher -> launcher.getAppsView().getWorkModeSwitch(), 60000);
UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
assertEquals(2, userManager.getUserProfiles().size());
@@ -102,10 +103,10 @@ public class WorkTabTest extends AbstractLauncherUiTest {
assertTrue(userManager.isQuietModeEnabled(workProfile));
executeOnLauncher(launcher -> {
WorkFooterContainer wf = launcher.getAppsView().getWorkFooterContainer();
WorkModeSwitch wf = launcher.getAppsView().getWorkModeSwitch();
((AllAppsPagedView) launcher.getAppsView().getContentView()).snapToPageImmediately(
AllAppsContainerView.AdapterHolder.WORK);
wf.getWorkModeSwitch().toggle();
wf.toggle();
});
waitForLauncherCondition("Work toggle did not work",
l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
@@ -158,9 +159,11 @@ public class WorkTabTest extends AbstractLauncherUiTest {
// dismiss personal edu
mDevice.pressHome();
waitForState("Launcher did not go home", () -> NORMAL);
// open work tab
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
waitForState("Launcher did not switch to all apps", () -> ALL_APPS);
executeOnLauncher(launcher -> {
AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
pagedView.setCurrentPage(WORK_PAGE);