diff --git a/buglist.txt b/buglist.txt deleted file mode 100644 index e1e95345e2..0000000000 --- a/buglist.txt +++ /dev/null @@ -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 diff --git a/buglist_unique.txt b/buglist_unique.txt deleted file mode 100644 index 7caec98a4d..0000000000 --- a/buglist_unique.txt +++ /dev/null @@ -1,19 +0,0 @@ -138964382 -139137636 -139828243 -143361609 -146593239 -147305863 -148867106 -149792636 -150122946 -150260456 -150522230 -150644839 -150680980 -150788630 -150864182 -150876921 -151025334 -151050221 -151166786 diff --git a/buglist_with_title.txt b/buglist_with_title.txt deleted file mode 100644 index 39a9bb4449..0000000000 --- a/buglist_with_title.txt +++ /dev/null @@ -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 ---- diff --git a/commitlist.txt b/commitlist.txt deleted file mode 100644 index e0df402838..0000000000 --- a/commitlist.txt +++ /dev/null @@ -1,777 +0,0 @@ -COMMAND>> git log f3779f129f7326cb7acb57bf6aabd68aca5b6218..3aaf3967348ff55d2b8ac6d50e59ea01d9362af9(B -commit 3aaf3967348ff55d2b8ac6d50e59ea01d9362af9 -Author: Vinit Nayak -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -Date: Sat Mar 7 07:19:13 2020 +0000 - - Merge "Enabling event verification for Launcher3" into ub-launcher3-master - -commit cfaa4889e65190b40ea988dd03421a01c9e06abc -Author: Sunny Goyal -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 -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 -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 -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 -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 -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 -Date: Fri Mar 6 23:33:26 2020 +0000 - - Merge "Initial changes to support blur" into ub-launcher3-master - -commit 4c9ee63540dcd2c039831edb0816a56458e30f8f -Author: Sunny Goyal -Date: Fri Mar 6 15:16:22 2020 -0800 - - Converting some anonymous classes to lambda calls - - Change-Id: I386046a4a515d84801a8bbd11cfa090ba7adfd71 - -commit 8687bc2131ce98ea0d058290dd21f03b5a429c82 -Author: Winson Chung -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 -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 -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 -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 -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 -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 -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 -Date: Fri Mar 6 02:14:19 2020 +0000 - - Merge "Catching everything from dumpHprofData" into ub-launcher3-master - -commit bb2bf277c0b22ecb1dc64d9971978d3c958dda44 -Author: vadimt -Date: Thu Mar 5 17:22:18 2020 -0800 - - Catching everything from dumpHprofData - - Change-Id: I79ced1d4bb3e6ea43ce6fa5bd07fe22b577006f9 - -commit 3abc8511a51afe983b481bdf6631535aa8b94f28 -Author: vadimt -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 -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 -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 diff --git a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml index b0f2b4bf8c..980bb5a805 100644 --- a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml +++ b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml @@ -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"/> diff --git a/quickstep/recents_ui_overrides/res/values/config.xml b/quickstep/recents_ui_overrides/res/values/config.xml index 527eec6457..120e03456f 100644 --- a/quickstep/recents_ui_overrides/res/values/config.xml +++ b/quickstep/recents_ui_overrides/res/values/config.xml @@ -14,8 +14,5 @@ limitations under the License. --> - 150 - 90 - 50 - 20 + 150 \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java new file mode 100644 index 0000000000..a5ea523a27 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java @@ -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; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 9afa862a44..0019ecb427 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -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; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java index 0ae7435907..b3bb850806 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java @@ -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; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index a07cd1d82c..da588173af 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -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 mPredictedApps; private HotseatEduDialog mActiveDialog; - private final NotificationManager mNotificationManager; - private final Notification mNotification; + private ArrayList 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 folders = new ArrayDeque<>(); + + ArrayList 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 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 animated = new ArrayList<>(); + ArrayList 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 predictedApps) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index 7986c269bc..f7db9eaa5b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -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 */ diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 2cdcd20732..d82e9f0115 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -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 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) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index 5bac964b1a..de3fce13ce 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -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; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 6a34917a26..024872fec4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -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 diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index 785a480174..6fc03b1bf1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -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(); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index b0125a893f..1b3610a92d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -80,6 +80,9 @@ public abstract class TaskViewTouchController } 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 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. diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index aaf7619dc6..ce7fa0d413 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -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 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 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 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 exten }); } anim.play(valueAnimator) - .with(createBackgroundBlurAnimator(blurController)); + .with(createDepthAnimator(depthController)); return anim; } @@ -197,14 +197,14 @@ final class AppToOverviewAnimationProvider 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); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index d402a75602..55e6ba2117 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -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 } 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 diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index 3ab0f19069..42d944fb78 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -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)); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java index 38b86cea43..7ec083e5df 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java @@ -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); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 25a307852d..eb5c7f9f69 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -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 { if (!insets.equals(mInsets)) { super.setInsets(insets); } - setBackground(insets.top == 0 ? null + setBackground(insets.top == 0 || !mAllowSysuiScrims + ? null : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim)); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java index 05c206f179..a87e7ebf6d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java @@ -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; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 893868bdf8..416d7a1bca 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -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); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 43c5cb5251..9cf45b3151 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -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.getOverviewPanel().getScroller().forceFinished(true); } + public AnimatorSet getAnimators() { + return mAnimators; + } + /** * Starts the animation. */ diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 3ed7530e6c..a027feaed5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -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 implements StateL } @Override - protected BackgroundBlurController getBackgroundBlurController() { - return mActivity.getBackgroundBlurController(); + protected DepthController getDepthController() { + return mActivity.getDepthController(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 8322d8c754..68c51a0b34 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -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 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 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 secondaryViewTranslate = mOrientationHandler.getSecondaryViewTranslate(); int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); @@ -1257,9 +1260,7 @@ public abstract class RecentsView 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 extends PagedView impl } } + @SuppressWarnings("WrongCall") private void onEnd(EndState endState) { if (endState.isSuccess) { if (shouldRemoveTask) { @@ -1290,15 +1292,18 @@ public abstract class RecentsView 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 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 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 extends PagedView impl } @Nullable - protected BackgroundBlurController getBackgroundBlurController() { + protected DepthController getDepthController() { return null; } @@ -2029,7 +2035,6 @@ public abstract class RecentsView extends PagedView impl void onEmptyMessageUpdated(boolean isEmpty); } - private static class PinnedStackAnimationListener extends IPinnedStackAnimationListener.Stub { private T mActivity; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index c94b56cade..56e3632dcd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -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 diff --git a/quickstep/res/layout/back_gesture_tutorial_fragment.xml b/quickstep/res/layout/back_gesture_tutorial_fragment.xml index 294e46e431..d8c25bd4d0 100644 --- a/quickstep/res/layout/back_gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/back_gesture_tutorial_fragment.xml @@ -16,9 +16,7 @@ - + + + #99000000 + \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index a7d00c5c72..ec66f115f9 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -244,7 +244,7 @@ public abstract class BaseQuickstepLauncher extends Launcher return new StateHandler[] { getAllAppsController(), getWorkspace(), - getBackgroundBlurController(), + getDepthController(), new RecentsViewStateController(this), new BackButtonAlphaHandler(this)}; } diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index b9bd6b1d54..c93a4ba566 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -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 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 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()); } } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java b/quickstep/src/com/android/launcher3/uioverrides/DepthController.java similarity index 53% rename from quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java rename to quickstep/src/com/android/launcher3/uioverrides/DepthController.java index 513310e945..8995a7e1a0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/DepthController.java @@ -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 BACKGROUND_BLUR = - new IntProperty("backgroundBlur") { + public static final FloatProperty DEPTH = + new FloatProperty("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 { - private final int mMinValue; - private final int mMaxValue; + public static class ClampedDepthProperty extends FloatProperty { + 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(); } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java index 548223a8fe..c7cce0b563 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java @@ -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(() -> { diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index 971d917445..93e02a111f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -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 diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index be0bdd8cac..2a569f5d35 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -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 { @Nullable T getCreatedActivity(); - default @Nullable BackgroundBlurController getBackgroundBlurController() { + @Nullable + default DepthController getDepthController() { return null; } diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java index 3e84e7d5ac..8efaeb98d9 100644 --- a/quickstep/src/com/android/quickstep/InputConsumer.java +++ b/quickstep/src/com/android/quickstep/InputConsumer.java @@ -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 diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index 3fe91a3a8b..5c2e9928a8 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -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 getTitleStringId(); diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java index 54408ceb6e..593b6952dd 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java @@ -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 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() { diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java new file mode 100644 index 0000000000..04cd2f49d6 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java @@ -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); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java new file mode 100644 index 0000000000..34eeafc676 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java @@ -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 CURRENT_ANGLE = + new FloatPropertyCompat("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 CURRENT_TRANSLATION = + new FloatPropertyCompat("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 CURRENT_VERTICAL_TRANSLATION = + new FloatPropertyCompat("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(); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java index 8081ad7ce1..48153664cd 100644 --- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java +++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java @@ -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 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 diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_mode_switch.xml similarity index 51% rename from res/layout/work_tab_footer.xml rename to res/layout/work_mode_switch.xml index 264e27375c..689dbe7638 100644 --- a/res/layout/work_tab_footer.xml +++ b/res/layout/work_mode_switch.xml @@ -13,39 +13,26 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - \ No newline at end of file + android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding" +/> \ No newline at end of file diff --git a/res/values/config.xml b/res/values/config.xml index df0f2330f0..1675a986d3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -142,6 +142,7 @@ 0.7 150 + 3dp 18dp @@ -170,8 +171,12 @@ @dimen/staggered_damping_ratio @dimen/staggered_stiffness + @dimen/unlock_staggered_velocity_dp_per_s @dimen/swipe_up_fling_min_visible_change @dimen/swipe_up_y_overshoot + + + diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 12b5fc1afa..bed8278bc7 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -180,7 +180,10 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch return null; } - protected static T getOpenView( + /** + * Returns a view matching FloatingViewType + */ + public static T getOpenView( ActivityContext activity, @FloatingViewType int type) { BaseDragLayer dragLayer = activity.getDragLayer(); if (dragLayer == null) return null; diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index e6f8a85e52..21a8fd406e 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -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); + } } diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 2b0da434a9..34d70673ae 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -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(); diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index e3eb38792a..9682d09cd1 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -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); } diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index a32fd1239f..ef02e87a6b 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -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); diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index 336e423373..b75a5e7987 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -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); diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 2ad84b9f9c..7414a88586 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -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. */ diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1413a5cd1f..a83a694c9c 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -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) { diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index df71f16ec7..6ee82cd509 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -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) { diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index 1bd82633eb..c07dd9d3f8 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -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) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index ead6018505..c42e480475 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -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 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 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 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 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 + "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 } } + // 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 .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 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 if (mFolderCreateBg != null) { mFolderCreateBg.animateToRest(); } - mFolderCreationAlarm.setOnAlarmListener(null); - mFolderCreationAlarm.cancelAlarm(); } private void cleanupAddToFolder() { @@ -2196,14 +2179,14 @@ public class Workspace extends PagedView 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 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 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 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 } } - 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 } 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); } } diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java index f8df5d7be7..0d7df2b44d 100644 --- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java +++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java @@ -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 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 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); } } diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java index 117296d5f6..ddb547ffb7 100644 --- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java @@ -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); diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 0b439ec362..6f7f8e6a29 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -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); } diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index e085ff00bc..dd0212a359 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -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; + } } } diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index c4b2f68c94..a6ef10a8f9 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -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); } diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index aadb297446..f935e4d40c 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -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 { @@ -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); + } } diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 958c86366c..f12789a90f 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -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. */ diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 9a25c47a74..a95a5e17e6 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -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); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index dfd36e71f6..b3f87f06a1 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -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." diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java index 75693c63d0..9b91a1de67 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -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; } diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index de7bc6d0e2..d0d9aaf947 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -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. diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java index 87461d57fe..d4ce3080c0 100644 --- a/src/com/android/launcher3/dragndrop/DragDriver.java +++ b/src/com/android/launcher3/dragndrop/DragDriver.java @@ -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 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 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 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 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 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; } -} diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 369bf283b1..e18ca547fd 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -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 { 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 { 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 { @Override public void setInsets(Rect insets) { super.setInsets(insets); - mWorkspaceScrim.onInsetsChanged(insets); + mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims); mOverviewScrim.onInsetsChanged(insets); } diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java index 2d19f36421..959602beb4 100644 --- a/src/com/android/launcher3/dragndrop/DragOptions.java +++ b/src/com/android/launcher3/dragndrop/DragOptions.java @@ -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; diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 145885a458..7c76d34e03 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -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())); diff --git a/src/com/android/launcher3/dragndrop/DraggableView.java b/src/com/android/launcher3/dragndrop/DraggableView.java new file mode 100644 index 0000000000..df9990278a --- /dev/null +++ b/src/com/android/launcher3/dragndrop/DraggableView.java @@ -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) { } +} diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java index 06b5c409de..7788f9366e 100644 --- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java +++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java @@ -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(); diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index 0bb3fbac52..ea1fbdb583 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -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); } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 2be8ff463d..365e76fa2b 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -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 views = getIconsInReadingOrder(); + info.rank = Utilities.boundToRange(info.rank, 0, views.size()); views.add(info.rank, icon); mContent.arrangeChildren(views); mItemsInvalidated = true; diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index f72e67438b..b83609e680 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -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(); diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index ab1ff102c6..f0d18ae63c 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -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; } diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index 94d30f69c3..ed9dfbbd79 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -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, diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java index 83349bc8f9..7b7ab5e18b 100644 --- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java +++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java @@ -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; diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java index a9d10d775d..1b70fde88c 100644 --- a/src/com/android/launcher3/logging/LoggerUtils.java +++ b/src/com/android/launcher3/logging/LoggerUtils.java @@ -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; diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 32fce0b5a3..fdfcef1c28 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -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()); diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 5af5ebb4f5..9bac259efe 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -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 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()); diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 1d14a8f039..cbc5257b2d 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -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) { diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java index 04741a165b..b0defd445a 100644 --- a/src/com/android/launcher3/util/VibratorWrapper.java +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -39,7 +39,7 @@ public class VibratorWrapper { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(VibratorWrapper::new); - private static final VibrationEffect EFFECT_CLICK = + public static final VibrationEffect EFFECT_CLICK = createPredefined(VibrationEffect.EFFECT_CLICK); /** diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 25748ae538..868c91d4ac 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -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 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 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 } 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; + } } diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java new file mode 100644 index 0000000000..478141ae3c --- /dev/null +++ b/src/com/android/launcher3/views/ClipIconView.java @@ -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 mFgTransYProperty = + new FloatPropertyCompat("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 mFgTransXProperty = + new FloatPropertyCompat("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(); + } +} diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index fa625ed930..3e2560f13c 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -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 mFgTransYProperty - = new FloatPropertyCompat("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 mFgTransXProperty - = new FloatPropertyCompat("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; } } diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java deleted file mode 100644 index d86d0ffd89..0000000000 --- a/src/com/android/launcher3/views/WorkFooterContainer.java +++ /dev/null @@ -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); - } -} diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index f3fd7ca4da..c1310e38fb 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -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); + } } diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java index 68b1595eed..104ad779d6 100644 --- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java @@ -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()); + } } diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java index 662e627d99..3c112740cf 100644 --- a/src/com/android/launcher3/widget/PendingItemDragHelper.java +++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java @@ -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 diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java similarity index 66% rename from src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java rename to src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java index 75f99a9ee9..7ad85e22c3 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java @@ -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 BACKGROUND_BLUR = - new IntProperty("backgroundBlur") { + public static final FloatProperty DEPTH = + new FloatProperty("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) {} diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 3d12248493..7cd656e811 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -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 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 condition) { + protected void waitForLauncherCondition(String + message, Function 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 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) { diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java index db2d97478a..8d571ff18c 100644 --- a/tests/src/com/android/launcher3/ui/WorkTabTest.java +++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java @@ -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);