Merge changes I74b09220,Id7e2db5f into main
* changes:
Don't animate bubbles in the initial state update
Suppress animation for bubbles that auto expand
diff --git a/Android.bp b/Android.bp
index 877f7bb..c4ea48a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -75,7 +75,7 @@
"androidx.test.uiautomator_uiautomator",
"androidx.preference_preference",
"SystemUISharedLib",
- "animationlib",
+ "//frameworks/libs/systemui:animationlib",
"launcher-testing-shared",
],
srcs: [
@@ -150,9 +150,9 @@
"androidx.cardview_cardview",
"androidx.window_window",
"com.google.android.material_material",
- "iconloader_base",
- "view_capture",
- "animationlib",
+ "//frameworks/libs/systemui:iconloader_base",
+ "//frameworks/libs/systemui:view_capture",
+ "//frameworks/libs/systemui:animationlib",
"SystemUI-statsd",
"launcher-testing-shared",
"androidx.lifecycle_lifecycle-common-java8",
diff --git a/OWNERS b/OWNERS
index efcf9f3..dd2d00e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -39,5 +39,9 @@
patmanning@google.com
helencheuk@google.com
+# Widget Picker team
+shamalip@google.com
+zakcohen@google.com
+
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 5d489f5..d086da4 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -21,10 +21,9 @@
<LinearLayout
android:id="@+id/action_buttons"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/overview_actions_height"
- android:layout_gravity="bottom"
- android:gravity="center_horizontal"
+ android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal">
<Button
@@ -36,17 +35,12 @@
android:text="@string/action_screenshot"
android:theme="@style/ThemeControlHighlightWorkspaceColor" />
- <Space
- android:id="@+id/action_split_space"
- android:layout_width="@dimen/overview_actions_button_spacing"
- android:layout_height="1dp"
- android:visibility="gone" />
-
<Button
android:id="@+id/action_split"
style="@style/OverviewActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/overview_actions_button_spacing"
android:text="@string/action_split"
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
diff --git a/quickstep/res/layout/taskbar_edu_circle_to_search.xml b/quickstep/res/layout/taskbar_edu_circle_to_search.xml
new file mode 100644
index 0000000..6c95f25
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu_circle_to_search.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.TaskbarEduTooltip.Title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/taskbar_edu_circle_to_search_title"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/circle_to_search_animation" />
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/circle_to_search_animation"
+ android:layout_width="@dimen/taskbar_edu_swipe_lottie_width"
+ android:layout_height="@dimen/taskbar_edu_swipe_lottie_height"
+ android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
+ app:layout_constraintBottom_toTopOf="@id/circle_to_search_text"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:lottie_rawRes="@raw/taskbar_edu_circle_to_search"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true" />
+
+ <TextView
+ android:id="@+id/circle_to_search_text"
+ style="@style/TextAppearance.TaskbarEduTooltip.Subtext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textSize="@dimen/taskbar_edu_circle_to_search_subtitle_text_size"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/circle_to_search_animation"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/quickstep/res/raw/taskbar_edu_circle_to_search.json b/quickstep/res/raw/taskbar_edu_circle_to_search.json
new file mode 100644
index 0000000..0dcccd6
--- /dev/null
+++ b/quickstep/res/raw/taskbar_edu_circle_to_search.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":431,"w":348,"h":218,"nm":"Omni_EDU_05_matted","ddd":0,"assets":[{"id":"comp_0","nm":"Omni_EDU_05","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-140,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-48,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":174,"s":[0,0]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":192,"s":[17,17]},{"i":{"x":[0.999,0.999],"y":[1,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":237,"s":[17,17]},{"t":249,"s":[0,0]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-48],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":174,"op":249,"st":40,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"c2s null","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":193,"s":[-56.933,-189.286,0],"to":[-0.023,-0.021,0],"ti":[0.092,0.088,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":194,"s":[-57.069,-189.414,0],"to":[-0.092,-0.088,0],"ti":[0.189,0.184,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":195,"s":[-57.488,-189.813,0],"to":[-0.189,-0.184,0],"ti":[0.29,0.293,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":196,"s":[-58.204,-190.519,0],"to":[-0.29,-0.293,0],"ti":[0.392,0.419,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":197,"s":[-59.226,-191.574,0],"to":[-0.392,-0.419,0],"ti":[0.492,0.566,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":198,"s":[-60.555,-193.035,0],"to":[-0.492,-0.566,0],"ti":[0.582,0.741,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":199,"s":[-62.177,-194.971,0],"to":[-0.582,-0.741,0],"ti":[0.641,0.957,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":200,"s":[-64.048,-197.48,0],"to":[-0.641,-0.957,0],"ti":[0.644,1.22,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":201,"s":[-66.021,-200.712,0],"to":[-0.644,-1.22,0],"ti":[0.59,1.52,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":202,"s":[-67.912,-204.803,0],"to":[-0.59,-1.52,0],"ti":[0.478,1.841,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":203,"s":[-69.561,-209.83,0],"to":[-0.478,-1.841,0],"ti":[0.309,2.174,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":204,"s":[-70.781,-215.849,0],"to":[-0.309,-2.174,0],"ti":[0.095,2.509,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":205,"s":[-71.414,-222.876,0],"to":[-0.095,-2.509,0],"ti":[-0.149,2.838,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":206,"s":[-71.348,-230.903,0],"to":[0.149,-2.838,0],"ti":[-0.493,3.137,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":207,"s":[-70.519,-239.902,0],"to":[0.493,-3.137,0],"ti":[-0.971,3.37,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":208,"s":[-68.392,-249.724,0],"to":[0.971,-3.37,0],"ti":[-1.521,3.501,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":209,"s":[-64.693,-260.12,0],"to":[1.521,-3.501,0],"ti":[-2.183,3.438,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":210,"s":[-59.267,-270.731,0],"to":[2.183,-3.438,0],"ti":[-3.014,2.996,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":211,"s":[-51.596,-280.747,0],"to":[3.014,-2.996,0],"ti":[-3.718,2.327,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":212,"s":[-41.184,-288.709,0],"to":[3.718,-2.327,0],"ti":[-4.093,1.634,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":213,"s":[-29.289,-294.708,0],"to":[4.093,-1.634,0],"ti":[-4.243,0.814,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":214,"s":[-16.628,-298.511,0],"to":[4.243,-0.814,0],"ti":[-4.168,-0.028,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":215,"s":[-3.828,-299.591,0],"to":[4.168,0.028,0],"ti":[-3.907,-0.628,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":216,"s":[8.377,-298.344,0],"to":[3.907,0.628,0],"ti":[-3.551,-1.009,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":217,"s":[19.614,-295.822,0],"to":[3.551,1.009,0],"ti":[-3.138,-1.314,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":218,"s":[29.681,-292.288,0],"to":[3.138,1.314,0],"ti":[-2.68,-1.566,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":219,"s":[38.443,-287.941,0],"to":[2.68,1.566,0],"ti":[-2.137,-1.816,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":220,"s":[45.761,-282.889,0],"to":[2.137,1.816,0],"ti":[-1.616,-1.954,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":221,"s":[51.267,-277.046,0],"to":[1.616,1.954,0],"ti":[-1.251,-1.903,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":222,"s":[55.456,-271.165,0],"to":[1.251,1.903,0],"ti":[-0.99,-1.774,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":223,"s":[58.77,-265.626,0],"to":[0.99,1.774,0],"ti":[-0.752,-1.637,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":224,"s":[61.394,-260.52,0],"to":[0.752,1.637,0],"ti":[-0.54,-1.496,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[63.282,-255.805,0],"to":[0.54,1.496,0],"ti":[-0.388,-1.34,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":226,"s":[64.634,-251.545,0],"to":[0.388,1.34,0],"ti":[-0.278,-1.183,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":227,"s":[65.61,-247.762,0],"to":[0.278,1.183,0],"ti":[-0.195,-1.031,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":228,"s":[66.303,-244.447,0],"to":[0.195,1.031,0],"ti":[-0.13,-0.885,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":229,"s":[66.778,-241.579,0],"to":[0.13,0.885,0],"ti":[-0.081,-0.748,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":230,"s":[67.085,-239.135,0],"to":[0.081,0.748,0],"ti":[-0.045,-0.619,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":231,"s":[67.265,-237.089,0],"to":[0.045,0.619,0],"ti":[-0.02,-0.498,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":232,"s":[67.354,-235.418,0],"to":[0.02,0.498,0],"ti":[-0.004,-0.385,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":233,"s":[67.382,-234.099,0],"to":[0.004,0.385,0],"ti":[0.004,-0.279,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":234,"s":[67.377,-233.109,0],"to":[-0.004,0.279,0],"ti":[0.006,-0.18,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":235,"s":[67.358,-232.425,0],"to":[-0.006,0.18,0],"ti":[0.004,-0.087,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":236,"s":[67.341,-232.028,0],"to":[-0.004,0.087,0],"ti":[0.001,-0.021,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":237,"s":[67.335,-231.9,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":248,"s":[67.335,-231.9,0],"to":[-0.008,0.042,0],"ti":[0.033,-0.177,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":249,"s":[67.289,-231.647,0],"to":[-0.033,0.177,0],"ti":[0.079,-0.372,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":250,"s":[67.135,-230.84,0],"to":[-0.079,0.372,0],"ti":[0.155,-0.591,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":251,"s":[66.815,-229.413,0],"to":[-0.155,0.591,0],"ti":[0.268,-0.834,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":252,"s":[66.203,-227.297,0],"to":[-0.268,0.834,0],"ti":[0.413,-1.109,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":253,"s":[65.206,-224.409,0],"to":[-0.413,1.109,0],"ti":[0.247,-0.628,0]},{"t":254,"s":[63.723,-220.641,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":193,"op":264,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":90,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.149,"y":0.701},"o":{"x":0.4,"y":0.002},"t":248,"s":[-8.212,-253.668,0],"to":[0,0,0],"ti":[0,0,0]},{"t":263,"s":[0,-227,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.149,"y":0.701},"o":{"x":0.4,"y":0.002},"t":248,"s":[{"i":[[3.564,-2.549],[3.809,-1.264],[12.238,3.984],[8.325,10.606],[-0.214,12.436],[-2.655,8.03],[-10.533,8.871],[-9.434,2.582],[-8.48,-3.643],[-9.227,-13.6],[-1.802,-9.359],[2.171,-9.596]],"o":[[-3.564,2.549],[-12.609,4.183],[-12.52,-4.076],[-7.722,-9.838],[0.146,-8.454],[4.364,-13.197],[5.142,-4.331],[9.891,-2.707],[11.828,5.081],[4.099,6.042],[2.254,11.707],[-3.907,17.266]],"v":[[53.514,60.456],[41.519,66.798],[2.462,64.221],[-33.286,39.729],[-45.626,-0.393],[-41.723,-21.691],[-18.543,-58.238],[5.442,-69.196],[39.994,-68.523],[68.082,-43.976],[77.052,-19.838],[77.406,14.145]],"c":true}]},{"t":263,"s":[{"i":[[7.701,-4.092],[8.127,0],[7.226,4.357],[4.092,7.701],[0,8.127],[-4.357,7.226],[-7.701,4.092],[-8.127,0],[-7.226,-4.357],[-4.092,-7.701],[0,-8.127],[4.357,-7.226]],"o":[[-6.709,3.565],[-9.053,0],[-7.447,-4.489],[-3.565,-6.709],[0,-9.053],[4.489,-7.447],[6.709,-3.565],[9.053,0],[7.447,4.489],[3.565,6.709],[0,9.053],[-4.489,7.447]],"v":[[22.492,42.415],[0,48],[-24.747,41.137],[-42.415,22.492],[-48,0],[-41.137,-24.747],[-22.492,-42.415],[0,-48],[24.747,-41.137],[42.415,-22.492],[48,0],[41.137,24.747]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":78.63,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 303","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":193,"s":[0]},{"t":237,"s":[63],"h":1},{"i":{"x":[0.149],"y":[0.701]},"o":{"x":[0.4],"y":[0.002]},"t":248,"s":[63]},{"t":263,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":193,"op":263,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[0]},{"t":289,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[2.625,-203,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[2.625,-221,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.5,1.3],[0,0],[0,0],[0,0],[-0.65,1.45],[-0.25,1.6],[0,0],[2.65,-2.25],[3.6,0],[2.9,2.85],[0,4.05],[-2.85,2.85],[-4.1,0],[-1.1,-0.3],[-1,-0.5],[0,0],[1.7,0.5],[1.8,0],[3.75,-3.75],[0,-5.3],[-3.75,-3.75],[-5.4,0],[-2,0.7]],"o":[[0,0],[0,0],[0,0],[1.05,-1.35],[0.7,-1.5],[0,0],[-0.7,3.35],[-2.6,2.2],[-4.1,0],[-2.85,-2.9],[0,-4.05],[2.9,-2.9],[1.2,0],[1.1,0.25],[0,0],[-1.45,-0.85],[-1.65,-0.5],[-5.35,0],[-3.75,3.75],[0,5.3],[3.75,3.75],[2.25,0],[2,-0.75]],"v":[[1.35,9.863],[21.075,29.587],[24.375,26.288],[4.575,6.637],[7.125,2.438],[8.55,-2.213],[3.975,-2.213],[-1.05,6.188],[-10.35,9.488],[-20.85,5.212],[-25.125,-5.213],[-20.85,-15.562],[-10.35,-19.913],[-6.9,-19.462],[-3.75,-18.337],[-0.45,-21.637],[-5.175,-23.663],[-10.35,-24.413],[-24,-18.788],[-29.625,-5.213],[-24,8.363],[-10.275,13.988],[-3.9,12.938]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-3.05,-3.05],[0,-4.4],[-3.05,3.05],[-4.4,0],[3.05,3.05],[0,4.4],[3.05,-3.05],[4.4,0]],"o":[[3.05,3.05],[0,-4.4],[3.05,-3.05],[-4.4,0],[-3.05,-3.05],[0,4.4],[-3.05,3.05],[4.4,0]],"v":[[9.3,-9.262],[13.875,1.913],[18.45,-9.262],[29.625,-13.837],[18.45,-18.413],[13.875,-29.587],[9.3,-18.413],[-1.875,-13.837]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":60,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[-100,-161,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[100,-161,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[100,-279,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".black","cl":"black","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[-100,-279,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[{"i":[[0,0],[7.26,-4.415],[4.064,-7.787],[0,-8]],"o":[[-9.118,0],[-7.478,4.547],[-3.462,6.633],[0,0]],"v":[[11,-37],[-13.903,-30.043],[-31.581,-11.176],[-37,11]],"c":false}]},{"t":290,"s":[{"i":[[0,0],[0,0],[0,-7.732],[0,0]],"o":[[0,0],[-7.732,0],[0,0],[0,0]],"v":[[11,-11],[3,-11],[-11,3],[-11,11]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Handle Top Left","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":63,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"Search","parent":12,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-166,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[0,-109,0]}],"ix":2,"l":2},"a":{"a":0,"k":[83,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":166,"h":45,"ip":135,"op":319,"st":135,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Flower","parent":12,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.5,-209,0],"ix":2,"l":2},"a":{"a":0,"k":[109,115,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":219,"h":231,"ip":122,"op":431,"st":122,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue50","cl":"blue50","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":266,"s":[0]},{"t":278,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":263,"s":[0,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"t":299,"s":[0,-220,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":263,"s":[92,92]},{"t":299,"s":[240,158]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":263,"s":[46]},{"t":299,"s":[12]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":263,"op":431,"st":60,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":3,"nm":"pan up","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.15,"y":1},"o":{"x":0.653,"y":0},"t":99,"s":[174,109,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.15,"y":0.15},"o":{"x":0.546,"y":0.546},"t":169,"s":[174,327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.15,"y":1},"o":{"x":0.653,"y":0},"t":357,"s":[174,327,0],"to":[0,0,0],"ti":[0,0,0]},{"t":427,"s":[174,109,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100,"ix":1}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100,"ix":2}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0,"ix":4}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0,"ix":5}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"long Press Over - Outward","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":95,"s":[100]},{"t":113,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":83,"s":[116,116]},{"t":113,"s":[136,136]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Long Pres Over","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":83,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"long Press Over","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":86,"s":[100]},{"t":95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.999,0.999],"y":[1,1]},"o":{"x":[0.001,0.001],"y":[0,0]},"t":33,"s":[40,40]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":83,"s":[120,120]},{"t":101,"s":[40,40]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Long Pres Over","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":0,"nm":"Action Key","parent":12,"refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.539,38,0],"ix":2,"l":2},"a":{"a":0,"k":[53.5,53.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":107,"h":107,"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"long Press Under","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":42,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[100]},{"t":95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":30,"s":[96,96]},{"i":{"x":[0.833,0.833],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":66,"s":[120,120]},{"i":{"x":[0.999,0.999],"y":[1,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":83,"s":[120,120]},{"t":101,"s":[106,106]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529422283,0.890196084976,0.988235294819,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Long Press Under","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".grey800","cl":"grey800","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,38,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[348,142],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Grey800","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue100","cl":"blue100","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[174,109,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[348,218],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.890196078431,0.988235294118,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":431,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"SearchPill","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"superG","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[0]},{"t":17,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[-3.898,0.128,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.239,0.128,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.311,0.128,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.286,0.128,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.519,0.128,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.81,0.128,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.898,0.128,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.431,0.128,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-43.055,0.128,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-46.404,0.128,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-48.712,0.128,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-50.434,0.128,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-51.781,0.128,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.869,0.128,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.768,0.128,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.522,0.128,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.162,0.128,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.71,0.128,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.182,0.128,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.591,0.128,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.947,0.128,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.257,0.128,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.526,0.128,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.761,0.128,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.965,0.128,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.142,0.128,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.295,0.128,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.425,0.128,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.537,0.128,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.63,0.128,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.707,0.128,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.769,0.128,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.818,0.128,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.854,0.128,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.853,0.128,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.679,0.128,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.301,0.128,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.701,0.128,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.039,0.128,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.46,0.128,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.983,0.128,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.588,0.128,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.255,0.128,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.97,0.128,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.723,0.128,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.507,0.128,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.316,0.128,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.147,0.128,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.996,0.128,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.861,0.128,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.74,0.128,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.631,0.128,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.534,0.128,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.446,0.128,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.367,0.128,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.297,0.128,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.234,0.128,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.178,0.128,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.128,0.128,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.084,0.128,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.046,0.128,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.012,0.128,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.96,0.128,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.913,0.128,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[24.102,22.628,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.788,0],[0.959,-2.878],[0,0],[-4.737,0],[-2.091,-1.959]],"o":[[-1.262,-1.202],[-3.161,0],[0,0],[1.99,-3.959],[3.272,0],[0,0]],"v":[[6.004,1.712],[1.358,-0.106],[-5.439,4.903],[-9.458,1.783],[1.358,-4.903],[9.458,-1.742]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917647063732,0.262745112181,0.207843139768,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[23.001,15.411],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-red","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.808],[-0.252,-0.727],[0,0],[0,1.959],[-0.828,1.636],[0,0],[0,0]],"o":[[0,0.808],[0,0],[-0.828,-1.636],[0,-1.959],[0,0],[0,0],[-0.242,0.727]],"v":[[2.277,0],[2.661,2.313],[-1.358,5.434],[-2.661,0],[-1.358,-5.434],[2.661,-5.434],[2.661,-2.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.670588254929,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[14.901,22.628],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-yellow","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[3.272,0],[1.99,3.959],[0,0],[0,0],[-3.161,0],[-1.091,0.727]],"o":[[-2,1.848],[-4.737,0],[0,0],[0,0],[0.959,2.878],[1.636,0],[0,0]],"v":[[9.413,1.964],[1.404,4.903],[-9.413,-1.783],[-9.413,-4.903],[-5.393,-4.903],[1.404,0.106],[5.514,-1.066]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.203921571374,0.658823549747,0.32549020648,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[22.955,29.844],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-green","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-0.798],[2.283,-2.111],[0,0],[0,0],[-0.293,1.495],[0,0],[0,0]],"o":[[0.121,0.737],[0,3.686],[0,0],[0,0],[1.273,-0.858],[0,0],[0,0],[0,0]],"v":[[5.61,-5.6],[5.802,-3.308],[2.207,5.6],[-1.692,5.6],[-1.692,2.57],[0.732,-1.045],[-5.802,-1.045],[-5.802,-5.6]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258823543787,0.521568655968,0.956862747669,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[30.161,26.208],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"g-blue","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"mic","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[0]},{"t":17,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[2.7,0.705,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.041,0.705,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.113,0.705,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[6.088,0.705,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[9.321,0.705,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[14.611,0.705,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.7,0.705,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.233,0.705,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.857,0.705,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.206,0.705,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.514,0.705,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.235,0.705,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.583,0.705,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.671,0.705,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.569,0.705,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.323,0.705,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.963,0.705,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.511,0.705,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.984,0.705,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.393,0.705,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.749,0.705,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.058,0.705,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.328,0.705,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.563,0.705,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.767,0.705,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.944,0.705,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.096,0.705,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.227,0.705,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.338,0.705,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.431,0.705,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.509,0.705,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.571,0.705,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.62,0.705,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.656,0.705,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.655,0.705,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.48,0.705,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.103,0.705,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.503,0.705,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.841,0.705,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.262,0.705,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.785,0.705,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.39,0.705,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.057,0.705,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.772,0.705,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.525,0.705,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.309,0.705,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.118,0.705,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.949,0.705,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.798,0.705,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.663,0.705,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.542,0.705,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.433,0.705,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.335,0.705,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.248,0.705,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.169,0.705,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.098,0.705,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.035,0.705,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.979,0.705,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.929,0.705,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.886,0.705,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.847,0.705,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.814,0.705,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.786,0.705,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.742,0.705,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[140.7,23.205,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.688,0],[0,0],[0.012,3.389],[0,0],[4.735,0],[1.554,1.554]],"o":[[1.113,1.113],[0,0],[3.377,0],[0,0],[0,4.723],[-2.361,0],[0,0]],"v":[[-5.573,0.049],[-1.242,1.848],[-1.242,1.848],[4.851,-4.282],[7.298,-4.282],[-1.242,4.282],[-7.298,1.774]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917647063732,0.262745112181,0.207843139768,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[141.954,27.475],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-red","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,2.361],[0,0],[0,0],[-1.101,-1.113],[0,0]],"o":[[0,0],[0,0],[0,1.688],[0,0],[-1.554,-1.554]],"v":[[-2.117,-3.028],[0.33,-3.028],[0.33,-3.028],[2.117,1.303],[0.392,3.028]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.670588254929,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[134.264,26.233],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-yellow","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[1.224,-1.884],[-1.224,-1.884],[-1.224,1.884],[1.224,1.884]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.203921571374,0.658823549747,0.32549020648,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[140.712,33.556],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-green","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[2.031,0],[0,0],[0,2.031],[0,0],[-2.031,0],[0,-2.031]],"o":[[0,2.031],[0,0],[-2.031,0],[0,0],[0,-2.031],[2.031,0],[0,0]],"v":[[3.658,4.282],[0,7.953],[0,7.953],[-3.671,4.282],[-3.671,-4.282],[0,-7.953],[3.671,-4.282]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258823543787,0.521568655968,0.956862747669,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[140.712,18.923],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mic-blue","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey800","cl":"grey800","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":3,"s":[0]},{"t":12,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[83,22.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":0,"s":[56,45]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":6,"s":[100,45]},{"i":{"x":[0.833,0.833],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":36,"s":[166,45]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":128,"s":[166,45]},{"t":164,"s":[154,45]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":87,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Grey 800","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Flower","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[109.453,193.777,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[36.517,1.481],[-36.517,1.481]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[5.448,5.448],"ix":2},"p":{"a":0,"k":[-12.903,0.471],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[5.448,5.448],"ix":2},"p":{"a":0,"k":[9.094,0.471],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[5.448,5.448],"ix":2},"p":{"a":0,"k":[2.554,0.471],"ix":3},"nm":"Ellipse Path 3","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[6.588,6.588],"ix":2},"p":{"a":0,"k":[-8.927,-0.099],"ix":3},"nm":"Ellipse Path 4","mn":"ADBE Vector Shape - Ellipse","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[6.588,6.588],"ix":2},"p":{"a":0,"k":[5.118,-0.099],"ix":3},"nm":"Ellipse Path 5","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union","np":7,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.952,63.598,0],"ix":2,"l":2},"a":{"a":0,"k":[24.952,23.598,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.753,0],[0,1.753],[1.753,0],[0,-1.753]],"o":[[1.753,0],[0,-1.753],[-1.753,0],[0,1.753]],"v":[[0,3.174],[3.174,0],[0,-3.174],[-3.174,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[21.137,19.31],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"flower 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.753,0],[0,1.753],[1.753,0],[0,-1.753]],"o":[[1.753,0],[0,-1.753],[-1.753,0],[0,1.753]],"v":[[0,3.174],[3.174,0],[0,-3.174],[-3.174,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[28.767,23.097],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"flower 2","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.753,0],[0,1.753],[1.753,0],[0,-1.753]],"o":[[1.753,0],[0,-1.753],[-1.753,0],[0,1.753]],"v":[[0,3.174],[3.174,0],[0,-3.174],[-3.174,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[21.75,27.886],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"flower 3","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".yellow400","cl":"yellow400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[63.114,63.125,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.392,1.281],[2.395,-2.283],[1.893,0.223],[0.557,-3.174],[1.726,-0.891],[-1.504,-2.896],[0.835,-1.671],[-2.951,-1.504],[-0.278,-1.782],[-3.341,0.446],[-1.392,-1.281],[-2.395,2.283],[-1.893,-0.278],[-0.557,3.174],[-1.726,0.891],[1.504,2.896],[-0.835,1.671],[2.951,1.504],[0.334,1.838],[3.341,-0.446]],"o":[[-2.45,-2.283],[-1.392,1.337],[-3.341,-0.501],[-0.334,1.838],[-2.951,1.504],[0.835,1.671],[-1.504,2.896],[1.671,0.835],[0.557,3.174],[1.949,-0.278],[2.45,2.283],[1.392,-1.281],[3.341,0.501],[0.334,-1.838],[2.951,-1.504],[-0.835,-1.671],[1.504,-2.896],[-1.726,-0.891],[-0.557,-3.174],[-1.949,0.278]],"v":[[4.316,-21.412],[-4.372,-21.412],[-9.551,-19.741],[-16.567,-14.841],[-19.797,-10.553],[-22.47,-2.59],[-22.47,2.701],[-19.797,10.664],[-16.623,14.897],[-9.606,19.797],[-4.372,21.412],[4.316,21.412],[9.551,19.797],[16.567,14.897],[19.797,10.609],[22.47,2.645],[22.47,-2.645],[19.797,-10.609],[16.567,-14.897],[9.551,-19.797]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294819,0.78823530674,0.203921571374,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[97.335,135.554,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[10.748,5.402]],"o":[[0,0],[-0.891,-40.374],[0,0]],"v":[[16.706,59.315],[16.706,7.288],[-16.706,-59.315]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":3,"nm":"â–½ Right Flower","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144.155,88.602,0],"ix":2,"l":2},"a":{"a":0,"k":[31.228,40.542,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".black","cl":"black","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45.33,17.598,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.184,0],[0,2.184],[2.184,0],[0,-2.184]],"o":[[2.184,0],[0,-2.184],[-2.184,0],[0,2.184]],"v":[[0,3.954],[3.954,0],[0,-3.954],[-3.954,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".red400","cl":"red400","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45.024,17.515,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.167,2.283],[4.678,-6.014],[2.283,-0.223],[-5.959,-4.733],[-0.167,-2.283],[-4.678,6.014],[-2.283,0.223],[5.959,4.733]],"o":[[-0.613,-7.574],[-1.448,1.782],[-7.629,0.668],[1.838,1.448],[0.613,7.574],[1.392,-1.838],[7.629,-0.668],[-1.838,-1.448]],"v":[[11.054,-9.829],[-3.87,-14.284],[-9.718,-11.11],[-14.228,3.926],[-11.054,9.829],[3.87,14.284],[9.718,11.11],[14.228,-3.926]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333337307,0.403921574354,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".green400","cl":"green400","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[22.485,51.377,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.244,-14.2],[-4.678,11.138],[7.407,2.729]],"o":[[7.741,4.121],[1.002,-2.951],[-5.457,-1.448]],"v":[[-14.689,4.256],[14.269,2.642],[7.586,-8.663]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".green400","cl":"green400","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.651,52.32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-10.414,5.68]],"o":[[1.726,-32.355],[0,0]],"v":[[-16.651,28.763],[16.651,-28.763]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[85.004,158.386,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-17.306]],"o":[[34.304,21.05],[0,0]],"v":[[-23.31,-36.483],[23.31,36.483]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[55.116,132.289,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.847,-0.501],[1.281,-3.62],[-0.78,-2.172],[-3.341,-0.891],[-3.898,2.951],[-2.339,4.01],[1.114,0.501]],"o":[[-4.177,0.39],[-0.668,1.893],[1.169,3.341],[4.733,0.668],[3.731,-2.84],[-1.114,-0.501],[-5.29,-2.228]],"v":[[-5.506,-9.773],[-14.416,-3.09],[-14.361,3.147],[-7.065,9.607],[5.91,6.655],[14.931,-4.315],[11.59,-5.819]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.652,111.9,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.179,3.007],[0.724,0.167],[2.228,-1.392],[-0.446,-4.622],[-4.288,-3.341],[0,-0.056],[-0.724,4.901]],"o":[[-0.668,-0.39],[-2.562,-0.613],[-4.065,2.617],[0.557,5.513],[0.501,0.39],[2.172,-4.511],[0.78,-5.235]],"v":[[4.896,-14.778],[2.836,-15.613],[-4.849,-14.221],[-9.917,-1.97],[-1.731,11.284],[5.23,15.851],[9.685,1.483]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[133.059,138.554,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[3.731,-15.203]],"o":[[-27.844,11.695],[0,0]],"v":[[19.018,-18.878],[-19.018,18.878]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[164.078,125.618,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-6.404,-1.726],[-2.673,2.45],[-0.334,2.005],[0.446,1.392],[4.455,0.613],[1.671,-0.835],[0.223,-0.446],[-0.39,-0.501]],"o":[[3.397,0.891],[1.281,-1.169],[0.278,-1.448],[-1.392,-4.511],[-3.564,-0.501],[-0.446,0.223],[-0.223,0.501],[3.787,5.235]],"v":[[1.642,9.094],[11.777,7.256],[14.339,2.467],[14.061,-1.876],[4.705,-9.506],[-12.057,-5.942],[-14.452,-4.661],[-13.505,-2.879]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.35686275363,0.72549021244,0.454901963472,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3.824,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Action Key","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"action icon","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[67.56,56.18,0],"ix":2,"l":2},"a":{"a":0,"k":[67.56,56.18,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.379],[3.379,0],[0,-3.379],[-3.379,0]],"o":[[0,-3.379],[-3.379,0],[0,3.379],[3.379,0]],"v":[[6.144,0],[0,-6.144],[-6.144,0],[0,6.144]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,6.789],[-6.789,0],[0,-6.789],[6.789,0]],"o":[[0,-6.789],[6.789,0],[0,6.789],[-6.789,0]],"v":[[-12.288,0],[0,-12.288],[12.288,0],[0,12.288]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.185,-3.229],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-1.101,1.239],[-1.26,-1.207],[0,0],[1.215,-1.215],[1.205,1.15],[0,0]],"o":[[1.159,-1.304],[0,0],[1.241,1.189],[-1.177,1.177],[0,0],[-1.199,-1.144]],"v":[[1.548,2.22],[5.978,2.043],[14.529,10.239],[14.575,14.619],[10.291,14.669],[1.723,6.491]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100.469,100.469],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Union","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[84.621,73.217],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"mag","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24.577,24.577],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.029,35.842],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"top R","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24.577,24.577],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 3","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[47.241,69.628],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"bottom L","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24.577,24.577],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[47.241,35.841],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"top L","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.603921592236,0.627451002598,0.65098041296,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Action (not dynamic)","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[53.25,53.25,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[106.5,106.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":32,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Action (not dynamic)","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[174,109,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[348,218],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":16,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":431,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Omni_EDU_05","tt":1,"tp":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[174,109,0],"ix":2,"l":2},"a":{"a":0,"k":[174,109,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":348,"h":218,"ip":0,"op":431,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index e45d9fd..31d4071 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -26,6 +26,18 @@
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
<string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
<string name="widget_holder_factory_class" translatable="false">com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory</string>
+ <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
+ <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
+ <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+ <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
+ <string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
+ <string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
+ <string name="taskbar_view_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarViewCallbacksFactory</string>
+ <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
+
+ <string name="nav_handle_long_press_handler_class" translatable="false"></string>
+ <string name="assist_utils_class" translatable="false"></string>
+ <string name="assist_state_manager_class" translatable="false"></string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
determines how many thumbnails will be fetched in the background. -->
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index caa949e..af1ab99 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -397,6 +397,7 @@
<dimen name="taskbar_edu_features_tooltip_width_with_one_feature">412dp</dimen>
<dimen name="taskbar_edu_features_tooltip_width_with_two_features">428dp</dimen>
<dimen name="taskbar_edu_features_tooltip_width_with_three_features">624dp</dimen>
+ <dimen name="taskbar_edu_circle_to_search_subtitle_text_size">12sp</dimen>
<!--- Taskbar Pinning -->
<dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
deleted file mode 100644
index cba1f5b..0000000
--- a/quickstep/res/values/override.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!-- Class overrides for launcher with quickstep. -->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
-
- <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
-
- <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
-
- <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
-
- <string name="nav_handle_long_press_handler_class" translatable="false"></string>
-
- <string name="assist_utils_class" translatable="false"></string>
-
- <string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
-
- <string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
-
- <string name="taskbar_view_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarViewCallbacksFactory</string>
-
- <string name="assist_state_manager_class" translatable="false"></string>
-
- <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
-
-</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 75a4fc8..eb9c5f0 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -273,6 +273,10 @@
<string name="taskbar_edu_pinning_title">Always show the Taskbar</string>
<!-- Text in dialog that shows a user how to pin the Taskbar. [CHAR_LIMIT 150] -->
<string name="taskbar_edu_pinning_standalone">To always show the Taskbar on the bottom of your screen, touch & hold the divider</string>
+ <!-- Title in dialog that shows a user how to invoke the Circle to Search feature. [CHAR_LIMIT 150] -->
+ <string name="taskbar_edu_circle_to_search_title">Touch & hold the action key to search what\'s on your screen</string>
+ <!-- Message showed to user to disclose privacy information they need to accept in order to access the app. [CHAR LIMIT=200]-->
+ <string name="taskbar_edu_circle_to_search_disclosure">This product uses the selected part of your screen to search. Google\'s <xliff:g example="https://policies.google.com/privacy/embedded" id="begin_privacy_link"><a href=\"%1$s\"></xliff:g>Privacy Policy<xliff:g id="end_privacy_link"></a></xliff:g> and <xliff:g example="https://policies.google.com/terms" id="begin_tos_link"><a href=\"%2$s\"></xliff:g>Terms of Service<xliff:g id="end_tos_link"></a></xliff:g> apply.</string>
<!-- Text on button to exit a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_close">Close</string>
<!-- Text on button to finish a tutorial [CHAR_LIMIT=16] -->
diff --git a/quickstep/src/com/android/launcher3/HomeTransitionController.java b/quickstep/src/com/android/launcher3/HomeTransitionController.java
deleted file mode 100644
index c4a2e9e..0000000
--- a/quickstep/src/com/android/launcher3/HomeTransitionController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.SystemUiProxy;
-import com.android.wm.shell.shared.IHomeTransitionListener;
-
-/**
- * Controls launcher response to home activity visibility changing.
- */
-public class HomeTransitionController {
-
- @Nullable private QuickstepLauncher mLauncher;
- @Nullable private IHomeTransitionListener mHomeTransitionListener;
-
- public void registerHomeTransitionListener(QuickstepLauncher launcher) {
- mLauncher = launcher;
- mHomeTransitionListener = new IHomeTransitionListener.Stub() {
- @Override
- public void onHomeVisibilityChanged(boolean isVisible) {
- MAIN_EXECUTOR.execute(() -> {
- if (mLauncher != null && mLauncher.getTaskbarUIController() != null) {
- mLauncher.getTaskbarUIController().onLauncherVisibilityChanged(isVisible);
- }
- });
- }
- };
-
- SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(mHomeTransitionListener);
- }
-
- public void unregisterHomeTransitionListener() {
- SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(null);
- mHomeTransitionListener = null;
- mLauncher = null;
- }
-}
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 8c4db4a..23cb8e9 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -35,23 +35,31 @@
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.dragndrop.SimpleDragLayer;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.WidgetPredictionsRequester;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
private static final String TAG = "WidgetPickerActivity";
-
/**
* Name of the extra that indicates that a widget being dragged.
*
@@ -64,14 +72,33 @@
// the intent, then widgets will not be filtered for size.
private static final String EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width";
private static final String EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height";
-
+ /**
+ * Widgets currently added by the user in the UI surface.
+ * <p>This allows widget picker to exclude existing widgets from suggestions.</p>
+ */
+ private static final String EXTRA_ADDED_APP_WIDGETS = "added_app_widgets";
+ /**
+ * A unique identifier of the surface hosting the widgets;
+ * <p>"widgets" is reserved for home screen surface.</p>
+ * <p>"widgets_hub" is reserved for glanceable hub surface.</p>
+ */
+ private static final String EXTRA_UI_SURFACE = "ui_surface";
+ private static final Pattern UI_SURFACE_PATTERN =
+ Pattern.compile("^(widgets|widgets_hub)$");
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
+ private LauncherAppState mApp;
+ private WidgetPredictionsRequester mWidgetPredictionsRequester;
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
private int mWidgetCategoryFilter;
+ @Nullable
+ private String mUiSurface;
+ // Widgets existing on the host surface.
+ @NonNull
+ private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -80,9 +107,8 @@
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
- LauncherAppState app = LauncherAppState.getInstance(this);
- InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
-
+ mApp = LauncherAppState.getInstance(this);
+ InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
mDeviceProfile = idp.getDeviceProfile(this);
mModel = new WidgetsModel();
@@ -97,6 +123,11 @@
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
+ parseIntentExtras();
+ refreshAndBindWidgets();
+ }
+
+ private void parseIntentExtras() {
// A value of 0 for either size means that no filtering will occur in that dimension. If
// both values are 0, then no size filtering will occur.
mDesiredWidgetWidth =
@@ -108,7 +139,15 @@
mWidgetCategoryFilter =
getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
- refreshAndBindWidgets();
+ String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE);
+ if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) {
+ mUiSurface = uiSurfaceParam;
+ }
+ ArrayList<AppWidgetProviderInfo> addedWidgets = getIntent().getParcelableArrayListExtra(
+ EXTRA_ADDED_APP_WIDGETS, AppWidgetProviderInfo.class);
+ if (addedWidgets != null) {
+ mAddedWidgets = addedWidgets;
+ }
}
@NonNull
@@ -179,11 +218,12 @@
};
}
+ /** Updates the model with widgets and provides them after applying the provided filter. */
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
- final ArrayList<WidgetsListBaseEntry> widgets =
+ final List<WidgetsListBaseEntry> allWidgets =
mModel.getFilteredWidgetsListForPicker(
app.getContext(),
/*widgetItemFilter=*/ widget -> {
@@ -193,10 +233,37 @@
return verdict.isAcceptable;
}
);
- MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
+ bindWidgets(allWidgets);
+ if (mUiSurface != null) {
+ Map<PackageUserKey, List<WidgetItem>> allWidgetsMap = allWidgets.stream()
+ .filter(WidgetsListHeaderEntry.class::isInstance)
+ .collect(Collectors.toMap(
+ entry -> PackageUserKey.fromPackageItemInfo(entry.mPkgItem),
+ entry -> entry.mWidgets)
+ );
+ mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
+ mUiSurface, allWidgetsMap);
+ mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
+ }
});
}
+ private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
+ MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
+ }
+
+ private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
+ MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets));
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mWidgetPredictionsRequester != null) {
+ mWidgetPredictionsRequester.clear();
+ }
+ }
+
private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
final AppWidgetProviderInfo info = widget.widgetInfo;
if (info == null) {
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index b36fd66..3e9272d 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -37,7 +37,6 @@
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.model.data.ItemInfo;
@@ -45,6 +44,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.views.ActivityContext;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -281,4 +281,11 @@
return getChildAt(0);
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled);
+ writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused);
+ writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow);
+ writer.println(prefix + "\tmPredictedApps: " + mPredictedApps);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index c0e0587..bc35125 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -111,12 +111,11 @@
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
private final StatsManager mStatsManager;
- private final Context mContext;
protected boolean mActive = false;
public QuickstepModelDelegate(Context context) {
- mContext = context;
+ super(context);
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
new file mode 100644
index 0000000..8431396
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model;
+
+import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.picker.WidgetRecommendationCategoryProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Works with app predictor to fetch and process widget predictions displayed in a standalone
+ * widget picker activity for a UI surface.
+ */
+public class WidgetPredictionsRequester {
+ private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
+ private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
+
+ @Nullable
+ private AppPredictor mAppPredictor;
+ private final Context mContext;
+ @NonNull
+ private final String mUiSurface;
+ @NonNull
+ private final Map<PackageUserKey, List<WidgetItem>> mAllWidgets;
+
+ public WidgetPredictionsRequester(Context context, @NonNull String uiSurface,
+ @NonNull Map<PackageUserKey, List<WidgetItem>> allWidgets) {
+ mContext = context;
+ mUiSurface = uiSurface;
+ mAllWidgets = Collections.unmodifiableMap(allWidgets);
+ }
+
+ /**
+ * Requests predictions from the app predictions manager and registers the provided callback to
+ * receive updates when predictions are available.
+ *
+ * @param existingWidgets widgets that are currently added to the surface;
+ * @param callback consumer of prediction results to be called when predictions are
+ * available
+ */
+ public void request(List<AppWidgetProviderInfo> existingWidgets,
+ Consumer<List<ItemInfo>> callback) {
+ Bundle bundle = buildBundleForPredictionSession(existingWidgets, mUiSurface);
+ Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
+
+ MODEL_EXECUTOR.execute(() -> {
+ clear();
+ AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
+ if (apm == null) {
+ return;
+ }
+
+ mAppPredictor = apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(mContext)
+ .setUiSurface(mUiSurface)
+ .setExtras(bundle)
+ .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
+ .build());
+ mAppPredictor.registerPredictionUpdates(MODEL_EXECUTOR,
+ targets -> bindPredictions(targets, filter, callback));
+ mAppPredictor.requestPredictionUpdate();
+ });
+ }
+
+ /**
+ * Returns a bundle that can be passed in a prediction session
+ *
+ * @param addedWidgets widgets that are already added by the user in the ui surface
+ * @param uiSurface a unique identifier of the surface hosting widgets; format
+ * "widgets_xx"; note - "widgets" is reserved for home screen surface.
+ */
+ @VisibleForTesting
+ static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets,
+ String uiSurface) {
+ Bundle bundle = new Bundle();
+ ArrayList<AppTargetEvent> addedAppTargetEvents = new ArrayList<>();
+ for (AppWidgetProviderInfo info : addedWidgets) {
+ ComponentName componentName = info.provider;
+ AppTargetEvent appTargetEvent = buildAppTargetEvent(uiSurface, info, componentName);
+ addedAppTargetEvents.add(appTargetEvent);
+ }
+ bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, addedAppTargetEvents);
+ return bundle;
+ }
+
+ /**
+ * Builds the AppTargetEvent for added widgets in a form that can be passed to the widget
+ * predictor.
+ * Also see {@link PredictionHelper}
+ */
+ private static AppTargetEvent buildAppTargetEvent(String uiSurface, AppWidgetProviderInfo info,
+ ComponentName componentName) {
+ AppTargetId appTargetId = new AppTargetId("widget:" + componentName.getPackageName());
+ AppTarget appTarget = new AppTarget.Builder(appTargetId, componentName.getPackageName(),
+ /*user=*/ info.getProfile()).setClassName(componentName.getClassName()).build();
+ return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
+ .setLaunchLocation(uiSurface).build();
+ }
+
+ /**
+ * Returns a filter to match {@link WidgetItem}s that don't exist on the UI surface.
+ */
+ @NonNull
+ @VisibleForTesting
+ static Predicate<WidgetItem> notOnUiSurfaceFilter(
+ List<AppWidgetProviderInfo> existingWidgets) {
+ Set<ComponentKey> existingComponentKeys = existingWidgets.stream().map(
+ widget -> new ComponentKey(widget.provider, widget.getProfile())).collect(
+ Collectors.toSet());
+ return widgetItem -> !existingComponentKeys.contains(widgetItem);
+ }
+
+ /** Provides the predictions returned by the predictor to the registered callback. */
+ @WorkerThread
+ private void bindPredictions(List<AppTarget> targets, Predicate<WidgetItem> filter,
+ Consumer<List<ItemInfo>> callback) {
+ List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
+ List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
+
+ MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
+ }
+
+ /**
+ * Applies the provided filter (e.g. widgets not on workspace) on the predictions returned by
+ * the predictor.
+ */
+ @VisibleForTesting
+ static List<WidgetItem> filterPredictions(List<AppTarget> predictions,
+ Map<PackageUserKey, List<WidgetItem>> allWidgets, Predicate<WidgetItem> filter) {
+ List<WidgetItem> servicePredictedItems = new ArrayList<>();
+ List<WidgetItem> localFilteredWidgets = new ArrayList<>();
+
+ for (AppTarget prediction : predictions) {
+ List<WidgetItem> widgetsInPackage = allWidgets.get(
+ new PackageUserKey(prediction.getPackageName(), prediction.getUser()));
+ if (widgetsInPackage == null || widgetsInPackage.isEmpty()) {
+ continue;
+ }
+ String className = prediction.getClassName();
+ if (!TextUtils.isEmpty(className)) {
+ WidgetItem item = widgetsInPackage.stream()
+ .filter(w -> className.equals(w.componentName.getClassName()))
+ .filter(filter)
+ .findFirst().orElse(null);
+ if (item != null) {
+ servicePredictedItems.add(item);
+ continue;
+ }
+ }
+ // No widget was added by the service, try local filtering
+ widgetsInPackage.stream().filter(filter).findFirst()
+ .ifPresent(localFilteredWidgets::add);
+ }
+ if (servicePredictedItems.isEmpty()) {
+ servicePredictedItems.addAll(localFilteredWidgets);
+ }
+
+ return servicePredictedItems;
+ }
+
+ /**
+ * Converts the list of {@link WidgetItem}s to the list of {@link ItemInfo}s.
+ */
+ private List<ItemInfo> mapWidgetItemsToItemInfo(List<WidgetItem> widgetItems) {
+ List<ItemInfo> items;
+ if (enableCategorizedWidgetSuggestions()) {
+ WidgetRecommendationCategoryProvider categoryProvider =
+ WidgetRecommendationCategoryProvider.newInstance(mContext);
+ items = widgetItems.stream()
+ .map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
+ categoryProvider.getWidgetRecommendationCategory(mContext, it)))
+ .collect(Collectors.toList());
+ } else {
+ items = widgetItems.stream().map(it -> new PendingAddWidgetInfo(it.widgetInfo,
+ CONTAINER_WIDGETS_PREDICTION)).collect(Collectors.toList());
+ }
+ return items;
+ }
+
+ /** Cleans up any open prediction sessions. */
+ public void clear() {
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ mAppPredictor = null;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index f9a8c99..176091a 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.os.Debug;
import android.os.SystemProperties;
@@ -136,9 +135,6 @@
Log.d(TAG, "setVisibleFreeformTasksCount: visibleTasksCount=" + visibleTasksCount
+ " currentValue=" + mVisibleFreeformTasksCount);
}
- if (!enableDesktopWindowingMode()) {
- return;
- }
if (visibleTasksCount != mVisibleFreeformTasksCount) {
final boolean wasVisible = mVisibleFreeformTasksCount > 0;
@@ -180,9 +176,6 @@
Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
+ " currentValue=" + mInOverviewState);
}
- if (!enableDesktopWindowingMode()) {
- return;
- }
if (overviewStateEnabled != mInOverviewState) {
mInOverviewState = overviewStateEnabled;
if (mInOverviewState) {
@@ -202,9 +195,6 @@
Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
+ " currentValue=" + mBackgroundStateEnabled);
}
- if (!enableDesktopWindowingMode()) {
- return;
- }
if (backgroundStateEnabled != mBackgroundStateEnabled) {
mBackgroundStateEnabled = backgroundStateEnabled;
if (mBackgroundStateEnabled) {
@@ -229,9 +219,6 @@
* Notify controller that recents gesture has started.
*/
public void setRecentsGestureStart() {
- if (!enableDesktopWindowingMode()) {
- return;
- }
if (DEBUG) {
Log.d(TAG, "setRecentsGestureStart");
}
@@ -243,9 +230,6 @@
* {@link com.android.quickstep.GestureState.GestureEndTarget}
*/
public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) {
- if (!enableDesktopWindowingMode()) {
- return;
- }
if (DEBUG) {
Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index bed85d7..d89f49b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.taskbar;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
-
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
@@ -117,9 +115,7 @@
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
final boolean onDesktop =
- enableDesktopWindowingMode()
- && desktopController != null
- && desktopController.areFreeformTasksVisible();
+ desktopController != null && desktopController.areFreeformTasksVisible();
if (mModel.isTaskListValid(mTaskListChangeId)) {
// When we are opening the KQS with no focus override, check if the first task is
@@ -158,14 +154,12 @@
// Hide all desktop tasks and show them on the hidden tile
int hiddenDesktopTasks = 0;
- if (enableDesktopWindowingMode()) {
- DesktopTask desktopTask = findDesktopTask(tasks);
- if (desktopTask != null) {
- hiddenDesktopTasks = desktopTask.tasks.size();
- tasks = tasks.stream()
- .filter(t -> !(t instanceof DesktopTask))
- .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
- }
+ DesktopTask desktopTask = findDesktopTask(tasks);
+ if (desktopTask != null) {
+ hiddenDesktopTasks = desktopTask.tasks.size();
+ tasks = tasks.stream()
+ .filter(t -> !(t instanceof DesktopTask))
+ .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
}
mTasks = tasks.stream()
.limit(MAX_TASKS)
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index a59aead..23380d6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -35,6 +34,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
@@ -49,8 +49,10 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.OnboardingPrefs;
+import com.android.quickstep.HomeVisibilityState;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
@@ -79,6 +81,7 @@
AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
private final QuickstepLauncher mLauncher;
+ private final HomeVisibilityState mHomeState;
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
dp -> {
@@ -87,6 +90,8 @@
mControllers.taskbarViewController.onRotationChanged(dp);
}
};
+ private final HomeVisibilityState.VisibilityChangeListener mVisibilityChangeListener =
+ this::onLauncherVisibilityChanged;
// Initialized in init.
private final TaskbarLauncherStateController
@@ -94,6 +99,7 @@
public LauncherTaskbarUIController(QuickstepLauncher launcher) {
mLauncher = launcher;
+ mHomeState = SystemUiProxy.INSTANCE.get(mLauncher).getHomeVisibilityState();
}
@Override
@@ -104,8 +110,11 @@
mControllers.getSharedState().sysuiStateFlags);
mLauncher.setTaskbarUIController(this);
-
- onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
+ mHomeState.addListener(mVisibilityChangeListener);
+ onLauncherVisibilityChanged(
+ Flags.useActivityOverlay()
+ ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed(),
+ true /* fromInit */);
onStashedInAppChanged(mLauncher.getDeviceProfile());
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
@@ -129,6 +138,7 @@
mLauncher.setTaskbarUIController(null);
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
+ mHomeState.removeListener(mVisibilityChangeListener);
updateTaskTransitionSpec(true);
}
@@ -209,9 +219,7 @@
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
final boolean onDesktop =
- enableDesktopWindowingMode()
- && desktopController != null
- && desktopController.areFreeformTasksVisible();
+ desktopController != null && desktopController.areFreeformTasksVisible();
if (onDesktop) {
isVisible = false;
}
@@ -234,7 +242,8 @@
@Override
public void refreshResumedState() {
- onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
+ onLauncherVisibilityChanged(Flags.useActivityOverlay()
+ ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed());
}
@Override
@@ -299,6 +308,8 @@
*/
public void showEduOnAppLaunch() {
if (!shouldShowEduOnAppLaunch()) {
+ // Called in case the edu finishes and circle to search edu is still pending
+ mControllers.taskbarEduTooltipController.maybeShowCircleToSearchEdu();
return;
}
@@ -312,6 +323,11 @@
mControllers.taskbarEduTooltipController.maybeShowSwipeEdu();
}
+ /** Will make the next onRecentsAnimationFinished() animation a no-op. */
+ public void setSkipNextRecentsAnimEnd() {
+ mTaskbarLauncherStateController.setSkipNextRecentsAnimEnd();
+ }
+
/**
* Returns {@code true} if a Taskbar education should be shown on application launch.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8769f11..390dec9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1233,13 +1233,13 @@
return;
}
- boolean findExactPairMatch = itemInfos.size() == 2;
+ boolean isLaunchingAppPair = itemInfos.size() == 2;
// Convert the list of ItemInfo instances to a list of ComponentKeys
List<ComponentKey> componentKeys =
itemInfos.stream().map(ItemInfo::getComponentKey).toList();
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
componentKeys,
- findExactPairMatch,
+ isLaunchingAppPair,
foundTasks -> {
@Nullable Task foundTask = foundTasks[0];
if (foundTask != null) {
@@ -1253,10 +1253,18 @@
}
}
- if (findExactPairMatch) {
- // We did not find the app pair we were looking for, so launch one.
- recents.getSplitSelectController().getAppPairsController().launchAppPair(
- (AppPairIcon) launchingIconView, -1 /*cuj*/);
+ if (isLaunchingAppPair) {
+ // Finish recents animation if it's running before launching to ensure
+ // we get both leashes for the animation
+ mControllers.uiController.setSkipNextRecentsAnimEnd();
+ recents.switchToScreenshot(() ->
+ recents.finishRecentsAnimation(true /*toRecents*/,
+ false /*shouldPip*/,
+ () -> recents
+ .getSplitSelectController()
+ .getAppPairsController()
+ .launchAppPair((AppPairIcon) launchingIconView,
+ -1 /*cuj*/)));
} else {
startItemInfoActivity(itemInfos.get(0), foundTask);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 7eed955..e53f627 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -15,7 +15,12 @@
*/
package com.android.launcher3.taskbar
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
+import android.text.SpannableString
+import android.text.method.LinkMovementMethod
+import android.text.style.URLSpan
import android.view.Gravity
import android.view.View
import android.view.View.GONE
@@ -24,16 +29,20 @@
import android.view.ViewGroup.MarginLayoutParams
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.TextView
import androidx.annotation.IntDef
import androidx.annotation.LayoutRes
+import androidx.core.text.HtmlCompat
import androidx.core.view.updateLayoutParams
import com.airbnb.lottie.LottieAnimationView
+import com.android.launcher3.LauncherPrefs
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.OnboardingPrefs.TASKBAR_CIRCLE_TO_SEARCH_EDU_SEEN
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.util.LottieAnimationColorUtils
@@ -52,6 +61,10 @@
* This value should match the maximum count for [TASKBAR_EDU_TOOLTIP_STEP].
*/
const val TOOLTIP_STEP_NONE = 3
+/** The base URL for the Privacy Policy that will later be localized. */
+private const val PRIVACY_POLICY_BASE_URL = "https://policies.google.com/privacy/embedded?hl="
+/** The base URL for the Terms of Service that will later be localized. */
+private const val TOS_BASE_URL = "https://policies.google.com/terms?hl="
/** Current step in the tooltip EDU flow. */
@Retention(AnnotationRetention.SOURCE)
@@ -70,6 +83,15 @@
get() = isTooltipEnabled && tooltipStep <= TOOLTIP_STEP_FEATURES
private lateinit var controllers: TaskbarControllers
+ // Keep track of whether the user has seen the Circle to Search Edu
+ private var userHasSeenCircleToSearchEdu: Boolean
+ get() {
+ return TASKBAR_CIRCLE_TO_SEARCH_EDU_SEEN.get(activityContext)
+ }
+ private set(seen) {
+ LauncherPrefs.get(activityContext).put(TASKBAR_CIRCLE_TO_SEARCH_EDU_SEEN, seen)
+ }
+
@TaskbarEduTooltipStep
var tooltipStep: Int
get() {
@@ -83,6 +105,8 @@
fun init(controllers: TaskbarControllers) {
this.controllers = controllers
+ // We want to show the Circle To Search Edu right after pinning, so we post it here
+ activityContext.dragLayer.post { maybeShowCircleToSearchEdu() }
}
/** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
@@ -112,6 +136,7 @@
fun maybeShowFeaturesEdu() {
if (!isTooltipEnabled || tooltipStep > TOOLTIP_STEP_FEATURES) {
maybeShowPinningEdu()
+ maybeShowCircleToSearchEdu()
return
}
@@ -207,6 +232,96 @@
}
}
+ /**
+ * Shows standalone Circle To Search EDU tooltip if this EDU has not been seen.
+ *
+ * We show this standalone edu for users to learn to how to trigger Circle To Search from the
+ * pinned taskbar
+ */
+ fun maybeShowCircleToSearchEdu() {
+ if (
+ !enableTaskbarPinning() ||
+ !DisplayController.isPinnedTaskbar(activityContext) ||
+ !isTooltipEnabled ||
+ userHasSeenCircleToSearchEdu
+ ) {
+ return
+ }
+ userHasSeenCircleToSearchEdu = true
+ inflateTooltip(R.layout.taskbar_edu_circle_to_search)
+ tooltip?.run {
+ requireViewById<LottieAnimationView>(R.id.circle_to_search_animation)
+ .supportLightTheme()
+ val eduSubtitle: TextView = requireViewById(R.id.circle_to_search_text)
+ showDisclosureText(eduSubtitle)
+ updateLayoutParams<BaseDragLayer.LayoutParams> {
+ if (DisplayController.isTransientTaskbar(activityContext)) {
+ bottomMargin += activityContext.deviceProfile.taskbarHeight
+ }
+ // Unlike other tooltips, we want to align with the all apps button rather than
+ // center.
+ gravity = Gravity.BOTTOM
+ marginStart = 0
+ width =
+ resources.getDimensionPixelSize(
+ R.dimen.taskbar_edu_features_tooltip_width_with_one_feature
+ )
+ }
+
+ // Calculate the amount the tooltip must be shifted by to align with the action key
+ val allAppsButtonView = controllers.taskbarViewController.allAppsButtonView
+ if (allAppsButtonView != null) {
+ val allAppsIconLocation = allAppsButtonView.x + allAppsButtonView.width / 2
+ x = allAppsIconLocation - layoutParams.width / 2
+ }
+
+ show()
+ }
+ }
+
+ /**
+ * Set up the provided TextView to display legal disclosures. The method takes locale into
+ * account to show the appropriate links to regional disclosures.
+ */
+ private fun TaskbarEduTooltip.showDisclosureText(
+ textView: TextView,
+ stringId: Int = R.string.taskbar_edu_circle_to_search_disclosure,
+ ) {
+ val locale = resources.configuration.locales[0]
+ val text =
+ SpannableString(
+ HtmlCompat.fromHtml(
+ resources.getString(
+ stringId,
+ PRIVACY_POLICY_BASE_URL + locale.language,
+ TOS_BASE_URL + locale.language,
+ ),
+ HtmlCompat.FROM_HTML_MODE_COMPACT,
+ )
+ )
+ // Directly process URLSpan clicks
+ text.getSpans(0, text.length, URLSpan::class.java).forEach { urlSpan ->
+ val url: URLSpan =
+ object : URLSpan(urlSpan.url) {
+ override fun onClick(widget: View) {
+ val uri = Uri.parse(urlSpan.url)
+ val context = widget.context
+ val intent =
+ Intent(Intent.ACTION_VIEW, uri).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+ }
+
+ val spanStart = text.getSpanStart(urlSpan)
+ val spanEnd = text.getSpanEnd(urlSpan)
+ val spanFlags = text.getSpanFlags(urlSpan)
+ text.removeSpan(urlSpan)
+ text.setSpan(url, spanStart, spanEnd, spanFlags)
+ }
+ textView.text = text
+ textView.movementMethod = LinkMovementMethod.getInstance()
+ }
+
/** Closes the current [tooltip]. */
fun hide() = tooltip?.close(true)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 7cad57b..8dc81cf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -163,6 +163,10 @@
setProviderInsets(provider, layoutParams.gravity, rotation)
}
}
+ // Also set the parent providers (i.e. not in paramsForRotation).
+ for (provider in windowLayoutParams.providedInsets) {
+ setProviderInsets(provider, windowLayoutParams.gravity, context.display.rotation)
+ }
context.notifyUpdateLayoutParams()
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 8d48154..b0abbe9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -148,6 +148,7 @@
private Integer mPrevState;
private int mState;
private LauncherState mLauncherState = LauncherState.NORMAL;
+ private boolean mSkipNextRecentsAnimEnd;
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
private long mLastUnlockTimeMs = 0;
@@ -292,12 +293,12 @@
if (mTaskBarRecentsAnimationListener != null) {
mTaskBarRecentsAnimationListener.endGestureStateOverride(
- !mLauncher.isInState(LauncherState.OVERVIEW));
+ !mLauncher.isInState(LauncherState.OVERVIEW), false /*canceled*/);
}
mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
callbacks.addListener(mTaskBarRecentsAnimationListener);
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
- mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
+ mTaskBarRecentsAnimationListener.endGestureStateOverride(true, false /*canceled*/));
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchCancelledRunnable(() -> {
updateStateForUserFinishedToApp(false /* finishedToApp */);
@@ -318,6 +319,11 @@
mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim;
}
+ /** Will make the next onRecentsAnimationFinished() a no-op. */
+ public void setSkipNextRecentsAnimEnd() {
+ mSkipNextRecentsAnimEnd = true;
+ }
+
/** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */
public void updateStateForSysuiFlags(int systemUiStateFlags) {
updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true);
@@ -656,7 +662,8 @@
* Returns if the current Launcher state has hotseat on top of other elemnets.
*/
public boolean isInHotseatOnTopStates() {
- return mLauncherState != LauncherState.ALL_APPS;
+ return mLauncherState != LauncherState.ALL_APPS
+ && !mLauncher.getWorkspace().isOverlayShown();
}
boolean isInOverview() {
@@ -770,19 +777,33 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW);
- endGestureStateOverride(!isInOverview);
+ endGestureStateOverride(!isInOverview, true /*canceled*/);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+ endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/);
}
- private void endGestureStateOverride(boolean finishedToApp) {
+ /**
+ * Handles whatever cleanup is needed after the recents animation is completed.
+ * NOTE: If {@link #mSkipNextRecentsAnimEnd} is set and we're coming from a non-cancelled
+ * path, this will not call {@link #updateStateForUserFinishedToApp(boolean)}
+ *
+ * @param finishedToApp {@code true} if the recents animation finished to showing an app and
+ * not workspace or overview
+ * @param canceled {@code true} if the recents animation was canceled instead of finishing
+ * to completion
+ */
+ private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
mCallbacks.removeListener(this);
mTaskBarRecentsAnimationListener = null;
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
+ if (mSkipNextRecentsAnimEnd && !canceled) {
+ mSkipNextRecentsAnimEnd = false;
+ return;
+ }
updateStateForUserFinishedToApp(finishedToApp);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index e293ad4..03f55ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -27,7 +27,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -281,12 +280,10 @@
private void navigateHome() {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
- if (enableDesktopWindowingMode()) {
- DesktopVisibilityController desktopVisibilityController =
- LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
- if (desktopVisibilityController != null) {
- desktopVisibilityController.onHomeActionTriggered();
- }
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController.onHomeActionTriggered();
}
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 4462f20..2730be1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -118,7 +118,7 @@
FolderInfo fi = (FolderInfo) info;
if (fi.anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : fi.getContents()) {
+ for (ItemInfo si : fi.getContents()) {
folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 3d58464..7e74c27 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -598,7 +598,8 @@
? stashTranslation : 0)
.setDuration(duration));
mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
- hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
+ (hasAnyFlag(FLAG_STASHED_IN_APP_IME) && isStashed) ? 0 : 1).setDuration(
+ duration));
mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
mAnimator = null;
mIsStashed = isStashed;
@@ -890,17 +891,11 @@
}
/**
- * Should be called when a system gesture starts and settles, so we can defer updating
- * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+ * Should be called when a system gesture starts and settles, so we can remove
+ * FLAG_STASHED_IN_APP_IME while the gesture is in progress.
*/
public void setSystemGestureInProgress(boolean inProgress) {
mIsSystemGestureInProgress = inProgress;
- if (mIsSystemGestureInProgress) {
- return;
- }
-
- // Only update the following flags when system gesture is not in progress.
- updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
setStashedImeState();
}
@@ -952,12 +947,9 @@
&& !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
- // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
-
- if (!mIsSystemGestureInProgress) {
- updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme());
+ if (updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme())) {
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
startDelay = getTaskbarStashStartDelayForIme();
}
@@ -970,7 +962,8 @@
*
* <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
* <p>Do not stash if taskbar is transient.
- * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked
+ * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked.
+ * <p>Do not stash if a system gesture is started.
*/
private boolean shouldStashForIme() {
if (DisplayController.isTransientTaskbar(mActivity)) {
@@ -996,6 +989,11 @@
return false;
}
+ // Do not stash if a gesture started.
+ if (mIsSystemGestureInProgress) {
+ return false;
+ }
+
return mIsImeShowing || mIsImeSwitcherShowing;
}
@@ -1007,13 +1005,16 @@
* @param flag The flag to update.
* @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
* unstashed.
+ * @return Whether the flag state changed.
*/
- public void updateStateForFlag(int flag, boolean enabled) {
+ public boolean updateStateForFlag(int flag, boolean enabled) {
+ int oldState = mState;
if (enabled) {
mState |= flag;
} else {
mState &= ~flag;
}
+ return mState != oldState;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 109400e..cb0fa40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -399,4 +399,12 @@
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, !isVisible);
mControllers.taskbarStashController.applyState();
}
+
+ /**
+ * Request for UI controller to ignore animations for the next callback for the end of recents
+ * animation
+ */
+ public void setSkipNextRecentsAnimEnd() {
+ // Overridden
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index dc2684e..effef3c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -32,6 +32,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
+import android.view.DisplayCutout;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -134,7 +135,7 @@
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
- if (enableTaskbarPinning()) {
+ if (enableTaskbarPinning() && !mActivityContext.isThreeButtonNav()) {
DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
actualIconSize = deviceProfile.taskbarIconSize;
}
@@ -472,6 +473,29 @@
iconEnd = centerAlignIconEnd + offset;
}
+ // Currently, we support only one device with display cutout and we only are concern about
+ // it when the bottom rect is present and non empty
+ DisplayCutout displayCutout = getDisplay().getCutout();
+ if (displayCutout != null && !displayCutout.getBoundingRectBottom().isEmpty()) {
+ Rect cutoutBottomRect = displayCutout.getBoundingRectBottom();
+ // when cutout present at the bottom of screen align taskbar icons to cutout offset
+ // if taskbar icon overlaps with cutout
+ int taskbarIconLeftBound = iconEnd - spaceNeeded;
+ int taskbarIconRightBound = iconEnd;
+
+ boolean doesTaskbarIconsOverlapWithCutout =
+ taskbarIconLeftBound <= cutoutBottomRect.centerX()
+ && cutoutBottomRect.centerX() <= taskbarIconRightBound;
+
+ if (doesTaskbarIconsOverlapWithCutout) {
+ if (!layoutRtl) {
+ iconEnd = spaceNeeded + cutoutBottomRect.width();
+ } else {
+ iconEnd = right - cutoutBottomRect.width();
+ }
+ }
+ }
+
sTmpRect.set(mIconLayoutBounds);
// Layout the children
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 1f7f0a7..5d0eac3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -337,11 +337,6 @@
private void updateTaskbarIconTranslationXForPinning() {
View[] iconViews = mTaskbarView.getIconViews();
float scale = mTaskbarIconTranslationXForPinning.value;
- float taskbarCenterX =
- mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
-
- float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
-
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonTranslationXOffset(true));
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
@@ -354,6 +349,17 @@
allAppIconTranslateRange *= -1;
}
+ if (mActivity.isThreeButtonNav()) {
+ ((IconButtonView) mTaskbarView.getAllAppsButtonView())
+ .setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange);
+ return;
+ }
+
+ float taskbarCenterX =
+ mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
+
+ float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
+
float halfIconCount = iconViews.length / 2.0f;
for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
View iconView = iconViews[iconIndex];
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 3b04602..99937f8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -207,10 +207,10 @@
}
@Override
- protected void onScaleProgressChanged() {
- super.onScaleProgressChanged();
- mAppsView.setClipChildren(!mIsBackProgressing);
- mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsBackProgressing);
+ protected void onUserSwipeToDismissProgressChanged() {
+ super.onUserSwipeToDismissProgressChanged();
+ mAppsView.setClipChildren(!mIsDismissInProgress);
+ mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsDismissInProgress);
}
@Override
@@ -264,7 +264,7 @@
if (mAllAppsCallbacks.handleSearchBackInvoked()) {
// We need to scale back taskbar all apps if we navigate back within search inside all
// apps
- animateSlideInViewToNoScale();
+ animateSwipeToDismissProgressToStart();
} else {
super.onBackInvoked();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 6e8b0de..a2c1b07 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -151,6 +151,9 @@
private BubbleStashController mBubbleStashController;
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
+ // Keep track of bubble bar bounds sent to shell to avoid sending duplicate updates
+ private final Rect mLastSentBubbleBarBounds = new Rect();
+
/**
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
* {@link BubbleBarBubble}s so that it can be used to update the views.
@@ -220,7 +223,8 @@
mBubbleStashedHandleViewController.setHiddenForBubbles(
!sBubbleBarEnabled || mBubbles.isEmpty());
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
- key -> setSelectedBubble(mBubbles.get(key)));
+ key -> setSelectedBubbleInternal(mBubbles.get(key)));
+ mBubbleBarViewController.setBoundsChangeListener(this::onBubbleBarBoundsChanged);
});
}
@@ -395,7 +399,7 @@
}
}
if (bubbleToSelect != null) {
- setSelectedBubble(bubbleToSelect);
+ setSelectedBubbleInternal(bubbleToSelect);
if (previouslySelectedBubble == null) {
mBubbleStashController.animateToInitialState(update.expanded);
}
@@ -414,8 +418,7 @@
if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) {
// Animate when receiving updates. Skip it if we received the initial state.
boolean animate = !update.initialState;
- mBubbleBarViewController.setBubbleBarLocation(update.bubbleBarLocation, animate);
- mBubbleStashController.setBubbleBarLocation(update.bubbleBarLocation);
+ updateBubbleBarLocationInternal(update.bubbleBarLocation, animate);
}
}
}
@@ -432,7 +435,9 @@
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
mSelectedBubble.getView().updateDotVisibility(true /* animate */);
}
- mSystemUiProxy.showBubble(getSelectedBubbleKey(), getExpandedBubbleBarDisplayBounds());
+ Rect bounds = getExpandedBubbleBarDisplayBounds();
+ mLastSentBubbleBarBounds.set(bounds);
+ mSystemUiProxy.showBubble(getSelectedBubbleKey(), bounds);
} else {
Log.w(TAG, "Trying to show the selected bubble but it's null");
}
@@ -441,7 +446,7 @@
/** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
public void showAndSelectBubble(BubbleBarItem b) {
if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey());
- setSelectedBubble(b);
+ setSelectedBubbleInternal(b);
showSelectedBubble();
}
@@ -450,7 +455,7 @@
* WMShell that the selection has changed, that should go through either
* {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
*/
- private void setSelectedBubble(BubbleBarItem b) {
+ private void setSelectedBubbleInternal(BubbleBarItem b) {
if (!Objects.equals(b, mSelectedBubble)) {
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
mSelectedBubble = b;
@@ -469,6 +474,21 @@
return null;
}
+ /**
+ * Set a new bubble bar location.
+ * <p>
+ * Updates the value locally in Launcher and in WMShell.
+ */
+ public void updateBubbleBarLocation(BubbleBarLocation location) {
+ updateBubbleBarLocationInternal(location, false /* animate */);
+ mSystemUiProxy.setBubbleBarLocation(location);
+ }
+
+ private void updateBubbleBarLocationInternal(BubbleBarLocation location, boolean animate) {
+ mBubbleBarViewController.setBubbleBarLocation(location, animate);
+ mBubbleStashController.setBubbleBarLocation(location);
+ }
+
//
// Loading data for the bubbles
//
@@ -600,27 +620,39 @@
return mIconFactory.createBadgedIconBitmap(drawable).icon;
}
+ private void onBubbleBarBoundsChanged(Rect newBounds) {
+ Rect displayBounds = convertToDisplayBounds(newBounds);
+ // Only send bounds over if they changed
+ if (!displayBounds.equals(mLastSentBubbleBarBounds)) {
+ mLastSentBubbleBarBounds.set(displayBounds);
+ mSystemUiProxy.setBubbleBarBounds(displayBounds);
+ }
+ }
+
/**
* Get bounds of the bubble bar as if it would be expanded.
* Calculates the bounds instead of retrieving current view location as the view may be
* animating.
*/
private Rect getExpandedBubbleBarDisplayBounds() {
+ return convertToDisplayBounds(mBarView.getBubbleBarBounds());
+ }
+
+ private Rect convertToDisplayBounds(Rect currentBarBounds) {
Point displaySize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
- Rect currentBarBounds = mBarView.getBubbleBarBounds();
- Rect location = new Rect();
+ Rect displayBounds = new Rect();
// currentBarBounds is only useful for distance from left or right edge.
// It contains the current bounds, calculate the expanded bounds.
if (mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl())) {
- location.left = currentBarBounds.left;
- location.right = (int) (currentBarBounds.left + mBarView.expandedWidth());
+ displayBounds.left = currentBarBounds.left;
+ displayBounds.right = (int) (currentBarBounds.left + mBarView.expandedWidth());
} else {
- location.left = (int) (currentBarBounds.right - mBarView.expandedWidth());
- location.right = currentBarBounds.right;
+ displayBounds.left = (int) (currentBarBounds.right - mBarView.expandedWidth());
+ displayBounds.right = currentBarBounds.right;
}
final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
- location.top = displaySize.y - currentBarBounds.height() - translation;
- location.bottom = displaySize.y - translation;
- return location;
+ displayBounds.top = displaySize.y - currentBarBounds.height() - translation;
+ displayBounds.bottom = displaySize.y - translation;
+ return displayBounds;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 4ca7c89..711ba62 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -24,7 +24,9 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.LayoutDirection;
@@ -94,6 +96,8 @@
private final BubbleBarBackground mBubbleBarBackground;
+ private boolean mIsAnimatingNewBubble = false;
+
/**
* The current bounds of all the bubble bar. Note that these bounds may not account for
* translation. The bounds should be retrieved using {@link #getBubbleBarBounds()} which
@@ -108,6 +112,7 @@
private final float mIconSize;
// The elevation of the bubbles within the bar
private final float mBubbleElevation;
+ private final float mDragElevation;
private final int mPointerSize;
// Whether the bar is expanded (i.e. the bubble activity is being displayed).
@@ -138,11 +143,15 @@
@Nullable
private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
+ private boolean mDragging;
+
@Nullable
private BubbleView mDraggedBubbleView;
private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
+ private boolean mLocationChangePending;
+
public BubbleBarView(Context context) {
this(context, null);
}
@@ -163,6 +172,7 @@
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
+ mDragElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_drag_elevation);
mPointerSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_pointer_size);
setClipToPadding(false);
@@ -237,12 +247,13 @@
}
}
+ @SuppressLint("RtlHardcoded")
private void onBubbleBarLocationChanged() {
+ mLocationChangePending = false;
final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
mBubbleBarBackground.setAnchorLeft(onLeft);
mRelativePivotX = onLeft ? 0f : 1f;
- ViewGroup.LayoutParams layoutParams = getLayoutParams();
- if (layoutParams instanceof LayoutParams lp) {
+ if (getLayoutParams() instanceof LayoutParams lp) {
lp.gravity = Gravity.BOTTOM | (onLeft ? Gravity.LEFT : Gravity.RIGHT);
setLayoutParams(lp);
}
@@ -267,11 +278,82 @@
}
}
+ /**
+ * Set whether this view is being currently being dragged
+ */
+ public void setIsDragging(boolean dragging) {
+ if (mDragging == dragging) {
+ return;
+ }
+ mDragging = dragging;
+ setElevation(dragging ? mDragElevation : mBubbleElevation);
+ if (!dragging && mLocationChangePending) {
+ // During drag finish animation we may update the translation x value to shift the
+ // bubble to the new drop target. Clear the translation here.
+ setTranslationX(0f);
+ onBubbleBarLocationChanged();
+ }
+ }
+
+ /**
+ * Adjust resting position for the bubble bar while it is being dragged.
+ * <p>
+ * Bubble bar is laid out on left or right side of the screen. When it is being dragged to
+ * the opposite side, the resting position should be on that side. Calculate any additional
+ * translation that may be required to move the bubble bar to the new side.
+ *
+ * @param restingPosition relative resting position of the bubble bar from the laid out position
+ */
+ @SuppressLint("RtlHardcoded")
+ void adjustRelativeRestingPosition(PointF restingPosition) {
+ final boolean locationOnLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
+ // Bubble bar is placed left or right with gravity. Check where it is currently.
+ final int absoluteGravity = Gravity.getAbsoluteGravity(
+ ((LayoutParams) getLayoutParams()).gravity, getLayoutDirection());
+ final boolean gravityOnLeft =
+ (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT;
+
+ // Bubble bar is pinned to the same side per gravity and the desired location.
+ // Resting translation does not need to be adjusted.
+ if (locationOnLeft == gravityOnLeft) {
+ return;
+ }
+
+ // Bubble bar is laid out on left or right side of the screen. And the desired new
+ // location is on the other side. Calculate x translation value required to shift the
+ // bubble bar from one side to the other.
+ float x = getDistanceFromOtherSide();
+ if (locationOnLeft) {
+ // New location is on the left, shift left
+ // before -> |......ooo.| after -> |.ooo......|
+ restingPosition.x = -x;
+ } else {
+ // New location is on the right, shift right
+ // before -> |.ooo......| after -> |......ooo.|
+ restingPosition.x = x;
+ }
+ }
+
+ private float getDistanceFromOtherSide() {
+ // Calculate the shift needed to position the bubble bar on the other side
+ int displayWidth = getResources().getDisplayMetrics().widthPixels;
+ int margin = 0;
+ if (getLayoutParams() instanceof MarginLayoutParams lp) {
+ margin += lp.leftMargin;
+ margin += lp.rightMargin;
+ }
+ return (float) (displayWidth - getWidth() - margin);
+ }
+
private void setBubbleBarLocationInternal(BubbleBarLocation bubbleBarLocation) {
if (bubbleBarLocation != mBubbleBarLocation) {
mBubbleBarLocation = bubbleBarLocation;
- onBubbleBarLocationChanged();
- invalidate();
+ if (mDragging) {
+ mLocationChangePending = true;
+ } else {
+ onBubbleBarLocationChanged();
+ invalidate();
+ }
}
}
@@ -377,6 +459,7 @@
/** Prepares for animating a bubble while being stashed. */
public void prepareForAnimatingBubbleWhileStashed(String bubbleKey) {
+ mIsAnimatingNewBubble = true;
// we're about to animate the new bubble in. the new bubble has already been added to this
// view, but we're currently stashed, so before we can start the animation we need make
// everything else in the bubble bar invisible, except for the bubble that's being animated.
@@ -397,6 +480,9 @@
/** Resets the state after the bubble animation completed. */
public void onAnimatingBubbleCompleted() {
+ mIsAnimatingNewBubble = false;
+ // setting the background triggers relayout so no need to explicitly invalidate after the
+ // animation
setBackground(mBubbleBarBackground);
for (int i = 0; i < getChildCount(); i++) {
final BubbleView view = (BubbleView) getChildAt(i);
@@ -441,6 +527,12 @@
* on the expanded state.
*/
private void updateChildrenRenderNodeProperties() {
+ if (mIsAnimatingNewBubble) {
+ // don't update bubbles if a new bubble animation is playing.
+ // the bubble bar will redraw itself via onLayout after the animation.
+ return;
+ }
+
final float widthState = (float) mWidthAnimator.getAnimatedValue();
final float currentWidth = getWidth();
final float expandedWidth = expandedWidth();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 256e2f0..aa1b4df 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -27,6 +27,7 @@
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
@@ -84,6 +85,11 @@
private BubbleBarViewAnimator mBubbleBarViewAnimator;
+ @Nullable
+ private BubbleBarBoundsChangeListener mBoundsChangeListener;
+
+ private final Rect mPreviousBubbleBarBounds = new Rect();
+
public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
mActivity = activity;
mBarView = barView;
@@ -113,9 +119,17 @@
mBubbleBarClickListener = v -> onBubbleBarClicked();
mBubbleDragController.setupBubbleBarView(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
- mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
- mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
- );
+ mBarView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ Rect bubbleBarBounds = mBarView.getBubbleBarBounds();
+ if (!bubbleBarBounds.equals(mPreviousBubbleBarBounds)) {
+ mPreviousBubbleBarBounds.set(bubbleBarBounds);
+ if (mBoundsChangeListener != null) {
+ mBoundsChangeListener.onBoundsChanged(bubbleBarBounds);
+ }
+ }
+ });
mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
}
@@ -431,4 +445,19 @@
public void onDismissAllBubblesWhileDragging() {
mSystemUiProxy.removeAllBubbles();
}
+
+ /**
+ * Set listener to be notified when bubble bar bounds have changed
+ */
+ public void setBoundsChangeListener(@Nullable BubbleBarBoundsChangeListener listener) {
+ mBoundsChangeListener = listener;
+ }
+
+ /**
+ * Listener to receive updates about bubble bar bounds changing
+ */
+ public interface BubbleBarBoundsChangeListener {
+ /** Called when bounds have changed */
+ void onBoundsChanged(Rect newBounds);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
index 39440ba..8b811d9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
@@ -18,7 +18,6 @@
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
-import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
import android.content.res.Resources;
import android.graphics.PointF;
@@ -42,11 +41,14 @@
private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
+ // 400f matches to MEDIUM_LOW spring stiffness
+ private static final float TRANSLATION_SPRING_STIFFNESS = 400f;
private final PhysicsAnimator.SpringConfig mDefaultConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
private final PhysicsAnimator.SpringConfig mTranslationConfig =
- new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
+ new PhysicsAnimator.SpringConfig(TRANSLATION_SPRING_STIFFNESS,
+ DAMPING_RATIO_LOW_BOUNCY);
@NonNull
private final View mView;
@NonNull
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index dab7d9d..5ffc6d8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -25,7 +25,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.R;
import com.android.launcher3.taskbar.TaskbarActivityContext;
/**
@@ -55,9 +54,8 @@
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleDismissController = bubbleControllers.bubbleDismissController;
mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
- mBubbleBarPinController.setListener(location -> {
- // TODO(b/330585397): update bubble bar location in shell
- });
+ mBubbleBarPinController.setListener(
+ bubbleControllers.bubbleBarController::updateBubbleBarLocation);
mBubbleDismissController.setListener(
stuck -> mBubbleBarPinController.setDropTargetHidden(stuck));
}
@@ -96,11 +94,8 @@
@SuppressLint("ClickableViewAccessibility")
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
PointF initialRelativePivot = new PointF();
- final int restingElevation = bubbleBarView.getResources().getDimensionPixelSize(
- R.dimen.bubblebar_elevation);
- final int dragElevation = bubbleBarView.getResources().getDimensionPixelSize(
- R.dimen.bubblebar_drag_elevation);
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
+
@Override
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
if (bubbleBarView.isExpanded()) return false;
@@ -114,7 +109,7 @@
// By default the bubble bar view pivot is in bottom right corner, while dragging
// it should be centered in order to align it with the dismiss target view
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
- bubbleBarView.setElevation(dragElevation);
+ bubbleBarView.setIsDragging(true);
mBubbleBarPinController.onDragStart(
bubbleBarView.getBubbleBarLocation().isOnLeft(bubbleBarView.isLayoutRtl()));
}
@@ -138,7 +133,14 @@
void onDragEnd() {
// Restoring the initial pivot for the bubble bar view
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
- bubbleBarView.setElevation(restingElevation);
+ bubbleBarView.setIsDragging(false);
+ }
+
+ @Override
+ protected PointF getRestingPosition() {
+ PointF restingPosition = super.getRestingPosition();
+ bubbleBarView.adjustRelativeRestingPosition(restingPosition);
+ return restingPosition;
}
});
}
@@ -226,6 +228,13 @@
protected void onDragDismiss() {
}
+ /**
+ * Get the resting position of the view when drag is released
+ */
+ protected PointF getRestingPosition() {
+ return mViewInitialPosition;
+ }
+
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
@@ -353,7 +362,7 @@
mAnimator.animateDismiss(mViewInitialPosition, onComplete);
} else {
onDragRelease();
- mAnimator.animateToInitialState(mViewInitialPosition, getCurrentVelocity(),
+ mAnimator.animateToInitialState(getRestingPosition(), getCurrentVelocity(),
onComplete);
}
mBubbleDismissController.hideDismissView();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index e25e586..61a2e22 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -51,6 +51,15 @@
*/
private static final float STASHED_BAR_SCALE = 0.5f;
+ /** The duration of hiding and showing the stashed handle as part of a new bubble animation. */
+ private static final long NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS = 200;
+
+ /** The translation Y value the handle animates to when hiding it for a new bubble. */
+ private static final int NEW_BUBBLE_HIDE_HANDLE_ANIMATION_TRANSLATION_Y = -20;
+
+ /** The alpha value the handle animates to when hiding it for a new bubble. */
+ public static final float NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA = 0.5f;
+
protected final TaskbarActivityContext mActivity;
// Initialized in init.
@@ -64,6 +73,7 @@
private AnimatedFloat mIconScaleForStash;
private AnimatedFloat mIconTranslationYForStash;
private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha;
+ private AnimatedFloat mBubbleStashedHandleTranslationY;
private boolean mRequestedStashState;
private boolean mRequestedExpandedState;
@@ -95,6 +105,7 @@
mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get(
StashedHandleViewController.ALPHA_INDEX_STASHED);
+ mBubbleStashedHandleTranslationY = mHandleViewController.getStashedHandleTranslationY();
mStashedHeight = mHandleViewController.getStashedHeight();
mUnstashedHeight = mHandleViewController.getUnstashedHeight();
@@ -362,4 +373,35 @@
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
mHandleViewController.setBubbleBarLocation(bubbleBarLocation);
}
+
+ /** Returns the x position of the center of the stashed handle. */
+ public float getStashedHandleCenterX() {
+ return mHandleViewController.getStashedHandleCenterX();
+ }
+
+ /** Returns the animation for hiding the handle before a new bubble animates in. */
+ public AnimatorSet buildHideHandleAnimationForNewBubble() {
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ mBubbleStashedHandleTranslationY.animateToValue(
+ NEW_BUBBLE_HIDE_HANDLE_ANIMATION_TRANSLATION_Y),
+ mBubbleStashedHandleAlpha.animateToValue(NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA));
+ animatorSet.setDuration(NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS);
+ return animatorSet;
+ }
+
+ /** Sets the alpha value of the stashed handle. */
+ public void setStashAlpha(float alpha) {
+ mBubbleStashedHandleAlpha.setValue(alpha);
+ }
+
+ /** Returns the animation for showing the handle after a new bubble animated in. */
+ public AnimatorSet buildShowHandleAnimationForNewBubble() {
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ mBubbleStashedHandleTranslationY.animateToValue(0),
+ mBubbleStashedHandleAlpha.animateToValue(1));
+ animatorSet.setDuration(NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS);
+ return animatorSet;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index f64517a..2a5912a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -29,6 +29,7 @@
import android.view.ViewOutlineProvider;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.taskbar.StashedHandleView;
@@ -47,7 +48,7 @@
private final TaskbarActivityContext mActivity;
private final StashedHandleView mStashedHandleView;
- private final MultiValueAlpha mTaskbarStashedHandleAlpha;
+ private final MultiValueAlpha mStashedHandleAlpha;
// Initialized in init.
private BubbleBarViewController mBarViewController;
@@ -58,6 +59,12 @@
private int mStashedHandleWidth;
private int mStashedHandleHeight;
+ private final AnimatedFloat mStashedHandleTranslationY =
+ new AnimatedFloat(this::updateTranslationY);
+
+ // Modified when swipe up is happening on the stashed handle or task bar.
+ private float mSwipeUpTranslationY;
+
// The bounds we want to clip to in the settled state when showing the stashed handle.
private final Rect mStashedHandleBounds = new Rect();
@@ -75,7 +82,7 @@
StashedHandleView stashedHandleView) {
mActivity = activity;
mStashedHandleView = stashedHandleView;
- mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
+ mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -93,7 +100,7 @@
R.dimen.transient_taskbar_bottom_margin);
mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin;
- mTaskbarStashedHandleAlpha.get(0).setValue(0);
+ mStashedHandleAlpha.get(0).setValue(0);
mStashedTaskbarHeight = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_size);
@@ -231,18 +238,33 @@
}
}
+ /** Returns an animator for translation Y. */
+ public AnimatedFloat getStashedHandleTranslationY() {
+ return mStashedHandleTranslationY;
+ }
+
/**
* Sets the translation of the stashed handle during the swipe up gesture.
*/
public void setTranslationYForSwipe(float transY) {
- mStashedHandleView.setTranslationY(transY);
+ mSwipeUpTranslationY = transY;
+ updateTranslationY();
+ }
+
+ private void updateTranslationY() {
+ mStashedHandleView.setTranslationY(mStashedHandleTranslationY.value + mSwipeUpTranslationY);
}
/**
* Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
*/
public MultiPropertyFactory<View> getStashedHandleAlpha() {
- return mTaskbarStashedHandleAlpha;
+ return mStashedHandleAlpha;
+ }
+
+ /** Returns the x position of the center of the stashed handle. */
+ public float getStashedHandleCenterX() {
+ return mStashedHandleBounds.exactCenterX();
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index bcb9f4d..1db5103 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -18,12 +18,16 @@
import android.view.View
import android.view.View.VISIBLE
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ObjectAnimator
+import androidx.core.animation.doOnEnd
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleStashController
import com.android.launcher3.taskbar.bubbles.BubbleView
+import com.android.systemui.util.doOnEnd
import com.android.wm.shell.shared.animation.PhysicsAnimator
/** Handles animations for bubble bar bubbles. */
@@ -39,7 +43,17 @@
/** The time to show the flyout. */
const val FLYOUT_DELAY_MS: Long = 2500
/** The translation Y the new bubble will animate to. */
- const val BUBBLE_ANIMATION_TRANSLATION_Y = -50f
+ const val BUBBLE_ANIMATION_FINAL_TRANSLATION_Y = -50f
+ /** The initial translation Y value the new bubble is set to before the animation starts. */
+ // TODO(liranb): get rid of this and calculate this based on the y-distance between the
+ // bubble and the stash handle.
+ const val BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y = 50f
+ /** The initial scale Y value that the new bubble is set to before the animation starts. */
+ const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
+ /** The initial alpha value that the new bubble is set to before the animation starts. */
+ const val BUBBLE_ANIMATION_INITIAL_ALPHA = 0.5f
+ /** The duration of the hide bubble animation. */
+ const val HIDE_BUBBLE_ANIMATION_DURATION_MS = 250L
}
/** An interface for scheduling jobs. */
@@ -78,41 +92,94 @@
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
// and the second part hides it after a delay.
val showAnimation = buildShowAnimation(bubbleView, b.key, animator)
- val hideAnimation = buildHideAnimation(animator)
+ val hideAnimation = buildHideAnimation(bubbleView)
scheduler.post(showAnimation)
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
}
- /** Returns a lambda that starts the animation that shows the new bubble. */
+ /**
+ * Returns a lambda that starts the animation that shows the new bubble.
+ *
+ * The animation is divided into 2 parts. First the stash handle starts animating up and fades
+ * out. When it ends the bubble starts fading in. The bubble and stashed handle are aligned to
+ * give the impression of the stash handle morphing into the bubble.
+ */
private fun buildShowAnimation(
bubbleView: BubbleView,
key: String,
- animator: PhysicsAnimator<BubbleView>
+ bubbleAnimator: PhysicsAnimator<BubbleView>
): () -> Unit = {
+ // calculate the initial translation x the bubble should have in order to align it with the
+ // stash handle.
+ val initialTranslationX =
+ bubbleStashController.stashedHandleCenterX - bubbleView.centerXOnScreen
bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
- animator.setDefaultSpringConfig(springConfig)
- animator
+ bubbleAnimator.setDefaultSpringConfig(springConfig)
+ bubbleAnimator
.spring(DynamicAnimation.ALPHA, 1f)
- .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_TRANSLATION_Y)
+ .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_FINAL_TRANSLATION_Y)
+ .spring(DynamicAnimation.SCALE_Y, 1f)
+ // prepare the bubble for the animation
bubbleView.alpha = 0f
+ bubbleView.translationX = initialTranslationX
+ bubbleView.translationY = BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y
+ bubbleView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
bubbleView.visibility = VISIBLE
- animator.start()
+ // start the stashed handle animation. when it ends, start the bubble animation.
+ val stashedHandleAnimation = bubbleStashController.buildHideHandleAnimationForNewBubble()
+ stashedHandleAnimation.doOnEnd {
+ bubbleView.alpha = BUBBLE_ANIMATION_INITIAL_ALPHA
+ bubbleAnimator.start()
+ bubbleStashController.setStashAlpha(0f)
+ }
+ stashedHandleAnimation.start()
}
- /** Returns a lambda that starts the animation that hides the new bubble. */
- private fun buildHideAnimation(animator: PhysicsAnimator<BubbleView>): () -> Unit = {
- animator.setDefaultSpringConfig(springConfig)
- animator
- .spring(DynamicAnimation.ALPHA, 0f)
- .spring(DynamicAnimation.TRANSLATION_Y, 0f)
- .addEndListener { _, _, _, canceled, _, _, allRelevantPropertyAnimsEnded ->
- if (!canceled && allRelevantPropertyAnimsEnded) {
- if (bubbleStashController.isStashed) {
- bubbleBarView.alpha = 0f
- }
- bubbleBarView.onAnimatingBubbleCompleted()
- }
+ /**
+ * Returns a lambda that starts the animation that hides the new bubble.
+ *
+ * Similarly to the show animation, this is divided into 2 parts. We first animate the bubble
+ * out, and then animate the stash handle in. At the end of the animation we reset the values of
+ * the bubble.
+ */
+ private fun buildHideAnimation(bubbleView: BubbleView): () -> Unit = {
+ val stashAnimation = bubbleStashController.buildShowHandleAnimationForNewBubble()
+ val alphaAnimator =
+ ObjectAnimator.ofFloat(bubbleView, View.ALPHA, BUBBLE_ANIMATION_INITIAL_ALPHA)
+ val translationYAnimator =
+ ObjectAnimator.ofFloat(
+ bubbleView,
+ View.TRANSLATION_Y,
+ BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y
+ )
+ val scaleYAnimator =
+ ObjectAnimator.ofFloat(bubbleView, View.SCALE_Y, BUBBLE_ANIMATION_INITIAL_SCALE_Y)
+ val hideBubbleAnimation = AnimatorSet()
+ hideBubbleAnimation.playTogether(alphaAnimator, translationYAnimator, scaleYAnimator)
+ hideBubbleAnimation.duration = HIDE_BUBBLE_ANIMATION_DURATION_MS
+ hideBubbleAnimation.doOnEnd {
+ // the bubble is now hidden, start the stash handle animation and reset bubble
+ // properties
+ bubbleStashController.setStashAlpha(
+ BubbleStashController.NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA
+ )
+ bubbleView.alpha = 0f
+ stashAnimation.start()
+ bubbleView.translationY = 0f
+ bubbleView.scaleY = 1f
+ if (bubbleStashController.isStashed) {
+ bubbleBarView.alpha = 0f
}
- animator.start()
+ bubbleBarView.onAnimatingBubbleCompleted()
+ }
+ hideBubbleAnimation.start()
}
}
+
+/** The X position in screen coordinates of the center of the bubble. */
+private val BubbleView.centerXOnScreen: Float
+ get() {
+ val screenCoordinates = IntArray(2)
+ getLocationOnScreen(screenCoordinates)
+ return screenCoordinates[0] + width / 2f
+ }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 9840791..34d3fad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -28,35 +28,39 @@
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
-/**
- * Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode.
- */
+/** Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode. */
class TaskbarNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup,
- imeSwitcher: ImageView?,
- rotationButton: RotationButton?,
- a11yButton: ImageView?,
- space: Space?
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- rotationButton,
- a11yButton,
- space
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
// Add spacing after the end of the last nav button
- var navMarginEnd = resources
- .getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing)
- .toInt()
+ var navMarginEnd =
+ resources.getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing).toInt()
+
+ val cutout = context.display.cutout
+ val bottomRect = cutout?.boundingRectBottom
+ if (bottomRect != null && !bottomRect.isEmpty) {
+ navMarginEnd = bottomRect.width()
+ }
+
val contextualWidth = endContextualContainer.width
// If contextual buttons are showing, we check if the end margin is enough for the
// contextual button to be showing - if not, move the nav buttons over a smidge
@@ -65,8 +69,11 @@
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
}
- val navButtonParams = FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ val navButtonParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
navButtonParams.apply {
gravity = Gravity.END or Gravity.CENTER_VERTICAL
marginEnd = navMarginEnd
@@ -98,18 +105,28 @@
startContextualContainer.removeAllViews()
if (!context.deviceProfile.isGestureMode) {
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
+ val contextualMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
- contextualMargin, Gravity.START)
+ repositionContextualContainer(
+ startContextualContainer,
+ WRAP_CONTENT,
+ contextualMargin,
+ contextualMargin,
+ Gravity.START
+ )
if (imeSwitcher != null) {
- val imeStartMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_ime_switcher_button_margin_start)
+ val imeStartMargin =
+ resources.getDimensionPixelSize(
+ R.dimen.taskbar_ime_switcher_button_margin_start
+ )
startContextualContainer.addView(imeSwitcher)
- val imeSwitcherButtonParams = FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ val imeSwitcherButtonParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
imeSwitcherButtonParams.apply {
marginStart = imeStartMargin
gravity = Gravity.CENTER_VERTICAL
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b49c752..c5c0092 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -62,8 +62,8 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
+import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import android.animation.Animator;
@@ -105,7 +105,6 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
-import com.android.launcher3.HomeTransitionController;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -217,7 +216,7 @@
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
private DepthController mDepthController;
- private DesktopVisibilityController mDesktopVisibilityController;
+ private @Nullable DesktopVisibilityController mDesktopVisibilityController;
private QuickstepTransitionManager mAppTransitionManager;
private OverviewActionsView mActionsView;
private TISBindHelper mTISBindHelper;
@@ -245,8 +244,6 @@
private boolean mIsPredictiveBackToHomeInProgress;
- private HomeTransitionController mHomeTransitionController;
-
@Override
protected void setupViews() {
super.setupViews();
@@ -277,15 +274,10 @@
mAppTransitionManager.registerRemoteAnimations();
mAppTransitionManager.registerRemoteTransitions();
- if (FeatureFlags.enableHomeTransitionListener()) {
- mHomeTransitionController = new HomeTransitionController();
- mHomeTransitionController.registerHomeTransitionListener(this);
- }
-
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
- mDesktopVisibilityController = new DesktopVisibilityController(this);
if (enableDesktopWindowingMode()) {
+ mDesktopVisibilityController = new DesktopVisibilityController(this);
mDesktopVisibilityController.registerSystemUiListener();
mSplitSelectStateController.initSplitFromDesktopController(this);
}
@@ -523,10 +515,6 @@
mLauncherUnfoldAnimationController.onDestroy();
}
- if (mHomeTransitionController != null) {
- mHomeTransitionController.unregisterHomeTransitionListener();
- }
-
if (mDesktopVisibilityController != null) {
mDesktopVisibilityController.unregisterSystemUiListener();
}
@@ -948,15 +936,13 @@
@Override
public void setResumed() {
- if (enableDesktopWindowingMode()) {
- DesktopVisibilityController controller = mDesktopVisibilityController;
- if (controller != null && controller.areFreeformTasksVisible()
- && !controller.isRecentsGestureInProgress()) {
- // Return early to skip setting activity to appear as resumed
- // TODO(b/255649902): shouldn't be needed when we have a separate launcher state
- // for desktop that we can use to control other parts of launcher
- return;
- }
+ if (mDesktopVisibilityController != null
+ && mDesktopVisibilityController.areFreeformTasksVisible()
+ && !mDesktopVisibilityController.isRecentsGestureInProgress()) {
+ // Return early to skip setting activity to appear as resumed
+ // TODO(b/255649902): shouldn't be needed when we have a separate launcher state
+ // for desktop that we can use to control other parts of launcher
+ return;
}
super.setResumed();
}
@@ -1090,6 +1076,7 @@
return mDepthController;
}
+ @Nullable
public DesktopVisibilityController getDesktopVisibilityController() {
return mDesktopVisibilityController;
}
@@ -1423,6 +1410,10 @@
if (mHotseatPredictionController != null) {
mHotseatPredictionController.dump(prefix, writer);
}
+ PredictionRowView<?> predictionRowView =
+ getAppsView().getFloatingHeaderView().findFixedRowByType(
+ PredictionRowView.class);
+ predictionRowView.dump(prefix, writer);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 7875dae..2625919 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -68,10 +68,13 @@
@Override
public void onBackInvoked(Launcher launcher) {
// In predictive back swipe, onBackInvoked() will be called after onBackStarted().
- // Because the 2nd InteractionJankMonitor.begin() will be ignore within timeout, it's safe
- // to call InteractionJankMonitorWrapper.begin here.
- InteractionJankMonitorWrapper.begin(launcher.getAppsView(),
- Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
+ // In 3 button mode, onBackStarted() is not called but onBackInvoked() will be called.
+ // Thus In onBackInvoked(), we should only begin instrumenting if we didn't call
+ // onBackStarted() to start instrumenting CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK.
+ if (!InteractionJankMonitorWrapper.isInstrumenting(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK)) {
+ InteractionJankMonitorWrapper.begin(
+ launcher.getAppsView(), Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
+ }
super.onBackInvoked(launcher);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index a443c00..547de77 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.content.Context;
import android.graphics.Color;
@@ -92,8 +91,7 @@
@Override
protected float getDepthUnchecked(Context context) {
- if (enableDesktopWindowingMode()
- && Launcher.getLauncher(context).areFreeformTasksVisible()) {
+ if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
// Don't blur the background while freeform tasks are visible
return BaseDepthController.DEPTH_0_PERCENT;
} else if (enableScalingRevealHomeAnimation()) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 2587395..7fb811d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.graphics.Color;
@@ -46,11 +45,9 @@
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
- if (enableDesktopWindowingMode()) {
- if (launcher.areFreeformTasksVisible()) {
- // No scrim while freeform tasks are visible
- return Color.TRANSPARENT;
- }
+ if (launcher.areFreeformTasksVisible()) {
+ // No scrim while freeform tasks are visible
+ return Color.TRANSPARENT;
}
DeviceProfile dp = launcher.getDeviceProfile();
if (dp.isTaskbarPresentInApps) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index dc1c6a6..62e823a 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -61,7 +61,6 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -947,7 +946,7 @@
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
- if (enableDesktopWindowingMode() && targets.hasDesktopTasks()) {
+ if (targets.hasDesktopTasks()) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
} else {
int untrimmedAppCount = mRemoteTargetHandles.length;
@@ -1170,13 +1169,11 @@
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify the SysUI to use fade-in animation when entering PiP
SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
- if (enableDesktopWindowingMode()) {
+ DesktopVisibilityController desktopVisibilityController =
+ mActivityInterface.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
// Notify the SysUI to stash desktop apps if they are visible
- DesktopVisibilityController desktopVisibilityController =
- mActivityInterface.getDesktopVisibilityController();
- if (desktopVisibilityController != null) {
- desktopVisibilityController.onHomeActionTriggered();
- }
+ desktopVisibilityController.onHomeActionTriggered();
}
break;
case RECENTS:
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 24c99e3..a3f6be0 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -25,7 +25,6 @@
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -109,19 +108,17 @@
if (endTarget != null) {
// We were on our way to this state when we got canceled, end there instead.
startState = stateFromGestureEndTarget(endTarget);
- if (enableDesktopWindowingMode()) {
- DesktopVisibilityController controller = getDesktopVisibilityController();
- if (controller != null && controller.areFreeformTasksVisible()
- && endTarget == LAST_TASK) {
- // When we are cancelling the transition and going back to last task, move to
- // rest state instead when desktop tasks are visible.
- // If a fullscreen task is visible, launcher goes to normal state when the
- // activity is stopped. This does not happen when freeform tasks are visible
- // on top of launcher. Force the launcher state to rest state here.
- startState = activity.getStateManager().getRestState();
- // Do not animate the transition
- activityVisible = false;
- }
+ DesktopVisibilityController controller = getDesktopVisibilityController();
+ if (controller != null && controller.areFreeformTasksVisible()
+ && endTarget == LAST_TASK) {
+ // When we are cancelling the transition and going back to last task, move to
+ // rest state instead when desktop tasks are visible.
+ // If a fullscreen task is visible, launcher goes to normal state when the
+ // activity is stopped. This does not happen when freeform tasks are visible
+ // on top of launcher. Force the launcher state to rest state here.
+ startState = activity.getStateManager().getRestState();
+ // Do not animate the transition
+ activityVisible = false;
}
}
activity.getStateManager().goToState(startState, activityVisible);
diff --git a/quickstep/src/com/android/quickstep/HomeVisibilityState.kt b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
new file mode 100644
index 0000000..241e16d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.os.RemoteException
+import android.util.Log
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.shared.IHomeTransitionListener.Stub
+import com.android.wm.shell.shared.IShellTransitions
+
+/** Class to track visibility state of Launcher */
+class HomeVisibilityState {
+
+ var isHomeVisible = true
+ private set
+
+ private var listeners = mutableSetOf<VisibilityChangeListener>()
+
+ fun addListener(l: VisibilityChangeListener) = listeners.add(l)
+
+ fun removeListener(l: VisibilityChangeListener) = listeners.remove(l)
+
+ fun init(transitions: IShellTransitions?) {
+ if (!FeatureFlags.enableHomeTransitionListener()) return
+ try {
+ transitions?.setHomeTransitionListener(
+ object : Stub() {
+ override fun onHomeVisibilityChanged(isVisible: Boolean) {
+ Executors.MAIN_EXECUTOR.execute {
+ isHomeVisible = isVisible
+ listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
+ }
+ }
+ }
+ )
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed call setHomeTransitionListener", e)
+ }
+ }
+
+ interface VisibilityChangeListener {
+ fun onHomeVisibilityChanged(isVisible: Boolean)
+ }
+
+ override fun toString() = "{HomeVisibilityState isHomeVisible=$isHomeVisible}"
+
+ companion object {
+
+ private const val TAG = "HomeVisibilityState"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 97c48e6..7c17e4e 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -35,6 +35,7 @@
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherInitListener;
@@ -206,8 +207,17 @@
@UiThread
private Launcher getVisibleLauncher() {
Launcher launcher = getCreatedActivity();
- return (launcher != null) && launcher.isStarted()
- && (isInLiveTileMode() || launcher.hasBeenResumed()) ? launcher : null;
+ if (launcher == null) {
+ return null;
+ }
+ if (launcher.isStarted() && (isInLiveTileMode() || launcher.hasBeenResumed())) {
+ return launcher;
+ }
+ if (Flags.useActivityOverlay()
+ && SystemUiProxy.INSTANCE.get(launcher).getHomeVisibilityState().isHomeVisible()) {
+ return launcher;
+ }
+ return null;
}
@Override
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 2d25295..79b09fd 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -39,6 +39,7 @@
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
+import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
@@ -99,7 +100,7 @@
private final int mWindowScaleMarginX;
private float mWindowScaleEndCornerRadius;
private float mWindowScaleStartCornerRadius;
- private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
+ private final Interpolator mProgressInterpolator = Interpolators.BACK_GESTURE;
private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final PointF mInitialTouchPos = new PointF();
@@ -302,7 +303,7 @@
if (mScrimLayer == null) {
addScrimLayer();
}
- mTransaction.apply();
+ applyTransaction();
}
private void setLauncherTargetViewVisible(boolean isVisible) {
@@ -342,7 +343,8 @@
return;
}
if (mScrimLayer.isValid()) {
- mTransaction.remove(mScrimLayer).apply();
+ mTransaction.remove(mScrimLayer);
+ applyTransaction();
}
mScrimLayer = null;
}
@@ -396,7 +398,11 @@
mTransaction.setWindowCrop(mBackTarget.leash, mStartRect);
mTransaction.setCornerRadius(mBackTarget.leash, cornerRadius);
}
+ applyTransaction();
+ }
+ private void applyTransaction() {
+ mTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
mTransaction.apply();
}
@@ -511,7 +517,7 @@
float value = (Float) animation.getAnimatedValue();
if (mScrimLayer != null && mScrimLayer.isValid()) {
mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
- mTransaction.apply();
+ applyTransaction();
}
});
mScrimAlphaAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 4c1b1e0..879fccb 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -270,9 +270,13 @@
int numVisibleTasks = 0;
for (GroupedRecentTaskInfo rawTask : rawTasks) {
- if (enableDesktopWindowingMode() && rawTask.getType() == TYPE_FREEFORM) {
- GroupTask desktopTask = createDesktopTask(rawTask);
- allTasks.add(desktopTask);
+ if (rawTask.getType() == TYPE_FREEFORM) {
+ // TYPE_FREEFORM tasks is only created when enableDesktopWindowingMode() is true,
+ // leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
+ if (enableDesktopWindowingMode()) {
+ GroupTask desktopTask = createDesktopTask(rawTask);
+ allTasks.add(desktopTask);
+ }
continue;
}
ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index ffbb064..0ce4d0a 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -17,7 +17,6 @@
package com.android.quickstep;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.app.WindowConfiguration;
@@ -68,17 +67,15 @@
* running tasks
*/
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
- if (enableDesktopWindowingMode()) {
- DesktopVisibilityController desktopVisibilityController =
- LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
- if (desktopVisibilityController != null) {
- int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
- if (visibleTasksCount > 0) {
- // Allocate +1 to account for a new task added to the desktop mode
- int numHandles = visibleTasksCount + 1;
- init(context, sizingStrategy, numHandles, true /* forDesktop */);
- return;
- }
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
+ if (visibleTasksCount > 0) {
+ // Allocate +1 to account for a new task added to the desktop mode
+ int numHandles = visibleTasksCount + 1;
+ init(context, sizingStrategy, numHandles, true /* forDesktop */);
+ return;
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index b6272da..ab609fd 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -63,7 +63,6 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.util.ScreenshotRequest;
import com.android.internal.view.AppearanceRegion;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.ActiveGestureLog;
@@ -82,6 +81,7 @@
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
@@ -91,7 +91,6 @@
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.shared.IHomeTransitionListener;
import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
@@ -157,7 +156,7 @@
private IOnBackInvokedCallback mBackToLauncherCallback;
private IRemoteAnimationRunner mBackToLauncherRunner;
private IDragAndDrop mDragAndDrop;
- private IHomeTransitionListener mHomeTransitionListener;
+ private final HomeVisibilityState mHomeVisibilityState = new HomeVisibilityState();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -269,7 +268,7 @@
setBubblesListener(mBubblesListener);
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
- setHomeTransitionListener(mHomeTransitionListener);
+ mHomeVisibilityState.init(mShellTransitions);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(
mLauncherActivityClass, mLauncherUnlockAnimationController);
@@ -455,6 +454,17 @@
}
@Override
+ public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.setOverrideHomeButtonLongPress(duration, slopMultiplier);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setOverrideHomeButtonLongPress", e);
+ }
+ }
+ }
+
+ @Override
public void notifyAccessibilityButtonClicked(int displayId) {
if (mSystemUiProxy != null) {
try {
@@ -808,6 +818,30 @@
}
}
+ /**
+ * Tells SysUI to update the bubble bar location to the new location.
+ * @param location new location for the bubble bar
+ */
+ public void setBubbleBarLocation(BubbleBarLocation location) {
+ try {
+ mBubbles.setBubbleBarLocation(location);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setBubbleBarLocation");
+ }
+ }
+
+ /**
+ * Tells SysUI the bounds for the bubble bar
+ * @param bubbleBarBounds bounds of the bubble bar in display coordinates
+ */
+ public void setBubbleBarBounds(Rect bubbleBarBounds) {
+ try {
+ mBubbles.setBubbleBarBounds(bubbleBarBounds);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setBubbleBarBounds");
+ }
+ }
+
//
// Splitscreen
//
@@ -1102,22 +1136,8 @@
mRemoteTransitions.remove(remoteTransition);
}
- public void setHomeTransitionListener(IHomeTransitionListener listener) {
- if (!FeatureFlags.enableHomeTransitionListener()) {
- return;
- }
-
- mHomeTransitionListener = listener;
-
- if (mShellTransitions != null) {
- try {
- mShellTransitions.setHomeTransitionListener(listener);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setHomeTransitionListener", e);
- }
- } else {
- Log.w(TAG, "Unable to call setHomeTransitionListener because ShellTransitions is null");
- }
+ public HomeVisibilityState getHomeVisibilityState() {
+ return mHomeVisibilityState;
}
/**
@@ -1558,7 +1578,7 @@
pw.println("\tmSplitSelectListener=" + mSplitSelectListener);
pw.println("\tmOneHanded=" + mOneHanded);
pw.println("\tmShellTransitions=" + mShellTransitions);
- pw.println("\tmHomeTransitionListener=" + mHomeTransitionListener);
+ pw.println("\tmHomeVisibilityState=" + mHomeVisibilityState);
pw.println("\tmStartingWindow=" + mStartingWindow);
pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index e4a8619..e22703b 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -34,6 +34,7 @@
import com.android.quickstep.NavHandle;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.TopTaskTracker;
+import com.android.quickstep.util.AssistStateManager;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
@@ -64,8 +65,9 @@
super(delegate, inputMonitor);
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
mDeepPressEnabled = FeatureFlags.ENABLE_LPNH_DEEP_PRESS.get();
- if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
- mLongPressTimeout = FeatureFlags.LPNH_TIMEOUT_MS.get();
+ AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(context);
+ if (assistStateManager.getLPNHDurationMillis().isPresent()) {
+ mLongPressTimeout = assistStateManager.getLPNHDurationMillis().get().intValue();
} else {
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index e078a49..a09e027 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -113,7 +113,7 @@
new CopyOnWriteArrayList<>();
public StatsLogCompatManager(Context context) {
- mContext = context;
+ super(context);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index f4da867..59bf105 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -153,7 +153,7 @@
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
- newAppPair.getContents().forEach(member -> {
+ newAppPair.getAppContents().forEach(member -> {
member.title = "";
member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
iconCache.getTitleAndIcon(member, member.usingLowResIcon());
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index a854656..a1fdbbb 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -52,6 +52,16 @@
return Optional.empty();
}
+ /** Get the Launcher overridden long press duration to trigger Assistant. */
+ public Optional<Long> getLPNHDurationMillis() {
+ return Optional.empty();
+ }
+
+ /** Get the Launcher overridden long press touch slop multiplier to trigger Assistant. */
+ public Optional<Long> getLPNHCustomSlopMultiplier() {
+ return Optional.empty();
+ }
+
/** Return {@code true} if the Settings toggle is enabled. */
public boolean isSettingsAllEntrypointsEnabled() {
return false;
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 6b27004..a2d3859 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -671,6 +671,7 @@
appIcon2,
dividerPos
)
+ floatingView.bringToFront()
// Launcher animation: animate the floating view, expanding to fill the display surface
progressUpdater.addUpdateListener(
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index d8cbbf9..bff5a25 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -35,7 +35,6 @@
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
@@ -70,6 +69,7 @@
import android.window.TransitionInfo;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.launcher3.Launcher;
@@ -132,6 +132,7 @@
private final StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
+ @Nullable
private SplitFromDesktopController mSplitFromDesktopController;
@Nullable
private DepthController mDepthController;
@@ -208,6 +209,9 @@
mActivityBackCallback = null;
mAppPairsController.onDestroy();
mSplitSelectDataHolder.onDestroy();
+ if (mSplitFromDesktopController != null) {
+ mSplitFromDesktopController.onDestroy();
+ }
}
/**
@@ -643,7 +647,12 @@
}
public void initSplitFromDesktopController(Launcher launcher) {
- mSplitFromDesktopController = new SplitFromDesktopController(launcher);
+ initSplitFromDesktopController(new SplitFromDesktopController(launcher));
+ }
+
+ @VisibleForTesting
+ void initSplitFromDesktopController(SplitFromDesktopController controller) {
+ mSplitFromDesktopController = controller;
}
private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId,
@@ -968,7 +977,6 @@
@Override
public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
int splitPosition, Rect taskBounds) {
- if (!enableDesktopWindowingMode()) return false;
MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
taskBounds));
return true;
@@ -977,6 +985,11 @@
SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener);
}
+ void onDestroy() {
+ SystemUiProxy.INSTANCE.get(mLauncher).unregisterSplitSelectListener(
+ mSplitSelectListener);
+ }
+
/**
* Enter split select from desktop mode.
* @param taskInfo the desktop task to move to split stage
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 2282c46..16d707b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -17,7 +17,6 @@
package com.android.quickstep.util;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -197,8 +196,6 @@
}
private boolean shouldIgnoreSecondSplitLaunch() {
- return (!FeatureFlags.enableSplitContextually()
- && !enableDesktopWindowingMode())
- || !mController.isSplitSelectActive();
+ return !FeatureFlags.enableSplitContextually() || !mController.isSplitSelectActive();
}
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index cd4fab6..0a3d2a0 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.annotation.TargetApi;
import android.content.Context;
@@ -265,11 +264,11 @@
@Override
public void onGestureAnimationEnd() {
- DesktopVisibilityController desktopVisibilityController = null;
+ DesktopVisibilityController desktopVisibilityController =
+ mActivity.getDesktopVisibilityController();
boolean showDesktopApps = false;
GestureState.GestureEndTarget endTarget = null;
- if (enableDesktopWindowingMode()) {
- desktopVisibilityController = mActivity.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
endTarget = mCurrentGestureEndTarget;
if (endTarget == GestureState.GestureEndTarget.LAST_TASK
&& desktopVisibilityController.areFreeformTasksVisible()) {
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 384a8d8..5188d4a 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -24,6 +24,7 @@
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -107,6 +108,7 @@
private MultiValueAlpha mMultiValueAlpha;
+ protected LinearLayout mActionButtons;
// The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is an
// ImageButton in go launcher (does not share a common class with Button). Take care when
// casting this.
@@ -151,7 +153,8 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), NUM_ALPHAS);
+ mActionButtons = findViewById(R.id.action_buttons);
+ mMultiValueAlpha = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
mMultiValueAlpha.setUpdateVisibility(true);
mScreenshotButton = findViewById(R.id.action_screenshot);
@@ -243,13 +246,13 @@
/**
* Updates a batch of flags to hide and show actions buttons for tablet/non tablet case.
- * @param isSmallScreen True if the current display is a small screen.
*/
- public void updateForSmallScreen(boolean isSmallScreen) {
+ private void updateForIsTablet() {
+ assert mDp != null;
// Update flags to see if split button should be hidden.
- updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, isSmallScreen);
+ updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, !mDp.isTablet);
// Update flags to see if save app pair button should be hidden.
- updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, isSmallScreen);
+ updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, !mDp.isTablet);
}
/**
@@ -274,7 +277,10 @@
mScreenshotButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mScreenshotButtonHiddenFlags == 0 ? VISIBLE : GONE;
- mScreenshotButton.setVisibility(desiredVisibility);
+ if (mScreenshotButton.getVisibility() != desiredVisibility) {
+ mScreenshotButton.setVisibility(desiredVisibility);
+ mActionButtons.requestLayout();
+ }
}
/**
@@ -292,8 +298,10 @@
mSplitButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mSplitButtonHiddenFlags == 0 ? VISIBLE : GONE;
- mSplitButton.setVisibility(desiredVisibility);
- findViewById(R.id.action_split_space).setVisibility(desiredVisibility);
+ if (mSplitButton.getVisibility() != desiredVisibility) {
+ mSplitButton.setVisibility(desiredVisibility);
+ mActionButtons.requestLayout();
+ }
}
/**
@@ -315,7 +323,10 @@
mAppPairButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mAppPairButtonHiddenFlags == 0 ? VISIBLE : GONE;
- mSaveAppPairButton.setVisibility(desiredVisibility);
+ if (mSaveAppPairButton.getVisibility() != desiredVisibility) {
+ mSaveAppPairButton.setVisibility(desiredVisibility);
+ mActionButtons.requestLayout();
+ }
}
public MultiProperty getContentAlpha() {
@@ -342,7 +353,7 @@
* Returns the visibility of the overview actions buttons.
*/
public @Visibility int getActionsButtonVisibility() {
- return findViewById(R.id.action_buttons).getVisibility();
+ return mActionButtons.getVisibility();
}
/**
@@ -358,8 +369,7 @@
if (mDp == null) {
return;
}
- LayoutParams actionParams = (LayoutParams) findViewById(
- R.id.action_buttons).getLayoutParams();
+ LayoutParams actionParams = (LayoutParams) mActionButtons.getLayoutParams();
actionParams.setMargins(
actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
actionParams.rightMargin, getBottomMargin());
@@ -386,6 +396,7 @@
mDp = dp;
mTaskSize.set(taskSize);
updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
+ updateForIsTablet();
requestLayout();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2aa16a9..eca70b7 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1061,6 +1061,8 @@
@Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+ // Update flags for 1p/3p launchers
+ mActionsView.updateFor3pLauncher(!supportsAppPairs());
mSplitSelectStateController = splitController;
mDesktopRecentsTransitionController = desktopRecentsTransitionController;
}
@@ -1251,16 +1253,23 @@
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
appAnimator.setInterpolator(ACCELERATE_DECELERATE);
+ final Matrix matrix = new Matrix();
appAnimator.addUpdateListener(valueAnimator -> {
float percent = valueAnimator.getAnimatedFraction();
SurfaceTransaction transaction = new SurfaceTransaction();
- Matrix matrix = new Matrix();
- matrix.postScale(percent, percent);
- matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2,
- mActivity.getDeviceProfile().heightPx * (1 - percent) / 2);
- transaction.forSurface(apps[apps.length - 1].leash)
- .setAlpha(percent)
- .setMatrix(matrix);
+ for (int i = apps.length - 1; i >= 0; --i) {
+ RemoteAnimationTarget app = apps[i];
+
+ float dx = mActivity.getDeviceProfile().widthPx * (1 - percent) / 2
+ + app.screenSpaceBounds.left * percent;
+ float dy = mActivity.getDeviceProfile().heightPx * (1 - percent) / 2
+ + app.screenSpaceBounds.top * percent;
+ matrix.setScale(percent, percent);
+ matrix.postTranslate(dx, dy);
+ transaction.forSurface(app.leash)
+ .setAlpha(percent)
+ .setMatrix(matrix);
+ }
surfaceApplier.scheduleApply(transaction);
});
appAnimator.addListener(new AnimatorListenerAdapter() {
@@ -3962,15 +3971,9 @@
// Update flags to see if actions bar should show buttons for a single task or a pair of
// tasks.
mActionsView.updateForGroupedTask(isCurrentSplit);
- // Update flags to see if actions bar should show buttons for tablets or phones.
- mActionsView.updateForSmallScreen(!mActivity.getDeviceProfile().isTablet);
- // Update flags for 1p/3p launchers
- mActionsView.updateFor3pLauncher(!supportsAppPairs());
- if (enableDesktopWindowingMode()) {
- boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
- mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
- }
+ boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
+ mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
}
/** Returns if app pairs are supported in this launcher. Overridden in subclasses. */
@@ -4692,9 +4695,7 @@
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
- if (enableDesktopWindowingMode()) {
- updateDesktopTaskVisibility(false /* visible */);
- }
+ updateDesktopTaskVisibility(false /* visible */);
}
/**
@@ -4716,9 +4717,7 @@
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
- if (enableDesktopWindowingMode()) {
- updateDesktopTaskVisibility(false /* visible */);
- }
+ updateDesktopTaskVisibility(false /* visible */);
}
private void updateDesktopTaskVisibility(boolean visible) {
@@ -4922,9 +4921,7 @@
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
- if (enableDesktopWindowingMode()) {
- updateDesktopTaskVisibility(true /* visible */);
- }
+ updateDesktopTaskVisibility(true /* visible */);
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -5367,6 +5364,10 @@
finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
}
+ /**
+ * NOTE: Whatever value gets passed through to the toRecents param may need to also be set on
+ * {@link #mRecentsAnimationController#setWillFinishToHome}.
+ */
public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
@Nullable Runnable onFinishComplete) {
Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: "
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 6a59ab4..a3e5a35 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -16,10 +16,12 @@
package com.android.quickstep.views;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.content.Context;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -33,12 +35,14 @@
import androidx.dynamicanimation.animation.SpringForce;
import com.android.app.animation.Interpolators;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.states.StateAnimationConfig;
import com.android.quickstep.util.SplitSelectStateController;
/**
@@ -51,6 +55,7 @@
public class SplitInstructionsView extends LinearLayout {
private static final int BOUNCE_DURATION = 250;
private static final float BOUNCE_HEIGHT = 20;
+ private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350;
private final StatefulActivity mLauncher;
public boolean mIsCurrentlyAnimating = false;
@@ -137,9 +142,24 @@
SplitSelectStateController splitSelectController =
((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController();
- splitSelectController.getSplitAnimationController().playPlaceholderDismissAnim(mLauncher,
- LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON);
- mLauncher.getStateManager().goToState(LauncherState.NORMAL);
+ StateManager stateManager = mLauncher.getStateManager();
+ BaseState startState = stateManager.getState();
+ long duration = startState.getTransitionDuration(mLauncher, false);
+ if (duration == 0) {
+ // Case where we're in contextual on workspace (NORMAL), which by default has 0
+ // transition duration
+ duration = DURATION_DEFAULT_SPLIT_DISMISS;
+ }
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = duration;
+ AnimatorSet stateAnim = stateManager.createAtomicAnimation(
+ startState, NORMAL, config);
+ AnimatorSet dismissAnim = splitSelectController.getSplitAnimationController()
+ .createPlaceholderDismissAnim(mLauncher,
+ LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, duration);
+ stateAnim.play(dismissAnim);
+ stateManager.setCurrentAnimation(stateAnim, NORMAL);
+ stateAnim.start();
}
void ensureProperRotation() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 79bd107..cec0982 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -645,6 +645,7 @@
*/
public void bind(Task task, RecentsOrientedState orientedState) {
cancelPendingLoadTasks();
+ testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.bind: task=" + task);
mTask = task;
mTaskIdContainer[0] = mTask.key.id;
mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, mIconView,
@@ -852,6 +853,8 @@
*/
@Nullable
public RunnableList launchTaskAnimated() {
+ testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+ "TaskView.launchTaskAnimated: mTask=" + mTask);
if (mTask != null) {
testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
"TaskView.launchTaskAnimated: startActivityFromRecentsAsync");
@@ -902,6 +905,7 @@
* Starts the task associated with this view without any animation
*/
public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
+ testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.launchTask: mTask=" + mTask);
if (mTask != null) {
testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
"TaskView.launchTask: startActivityFromRecentsAsync");
@@ -972,6 +976,9 @@
public RunnableList launchTasks() {
RecentsView recentsView = getRecentsView();
RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
+ testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+ "TaskView.launchTasks: isRunningTask=" + isRunningTask() + ", "
+ + "remoteTargetHandles == null?" + (remoteTargetHandles == null));
if (isRunningTask() && remoteTargetHandles != null) {
if (!mIsClickableAsLiveTile) {
Log.e(TAG, "TaskView is not clickable as a live tile; returning to home.");
@@ -999,7 +1006,7 @@
// If the recents animation is cancelled somehow between the parent if block and
// here, try to launch the task as a non live tile task.
testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
- "TaskView.java - launchTasks: recents animation is cancelled");
+ "TaskView.launchTasks: recents animation is cancelled");
RunnableList runnableList = launchTaskAnimated();
if (runnableList == null) {
Log.e(TAG, "Recents animation cancelled and cannot launch task as non-live tile"
@@ -1021,7 +1028,7 @@
public void onAnimationEnd(Animator animator) {
if (mTask != null && mTask.key.displayId != getRootViewDisplayId()) {
testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
- "TaskView.java - launchTasks: onAnimationEnd");
+ "TaskView.launchTasks: onAnimationEnd");
launchTaskAnimated();
}
mIsClickableAsLiveTile = true;
@@ -1041,9 +1048,6 @@
recentsView.onTaskLaunchedInLiveTileMode();
return runnableList;
} else {
- testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
- "TaskView.java - launchTasks: isRunningTask=" + isRunningTask() + "||"
- + "remoteTargetHandles == null?" + (remoteTargetHandles == null));
return launchTaskAnimated();
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index c17aeaa..b478efa 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -16,6 +16,9 @@
package com.android.launcher3.taskbar.bubbles.animation
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
import android.content.Context
import android.graphics.Color
import android.graphics.Path
@@ -24,6 +27,8 @@
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
+import androidx.core.animation.doOnEnd
import androidx.core.graphics.drawable.toBitmap
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.test.core.app.ApplicationProvider
@@ -39,10 +44,14 @@
import com.android.wm.shell.common.bubbles.BubbleInfo
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@SmallTest
@@ -52,6 +61,10 @@
private val context = ApplicationProvider.getApplicationContext<Context>()
private val animatorScheduler = TestBubbleBarViewAnimatorScheduler()
+ companion object {
+ @JvmField @ClassRule val animatorTestRule = AnimatorTestRule()
+ }
+
@Before
fun setUp() {
PhysicsAnimatorTestUtils.prepareForTest()
@@ -86,6 +99,15 @@
val bubbleStashController = mock<BubbleStashController>()
whenever(bubbleStashController.isStashed).thenReturn(true)
+ val semaphore = Semaphore(0)
+ val hideHandleAnimator = AnimatorSet()
+ hideHandleAnimator.duration = 0
+ whenever(bubbleStashController.buildHideHandleAnimationForNewBubble())
+ .thenReturn(hideHandleAnimator)
+ // add an end listener to the hide handle animation. we add it when the animation starts
+ // to ensure that it gets called after all other end listeners.
+ hideHandleAnimator.doOnStart { hideHandleAnimator.doOnEnd { semaphore.release() } }
+
val animator =
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
@@ -93,29 +115,44 @@
animator.animateBubbleInForStashed(bubble)
}
+ // wait for the stash handle animation to complete
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ // stash handle animation finished. verify that the stash handle is now hidden
+ verify(bubbleStashController).setStashAlpha(0f)
+
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
assertThat(overflowView.visibility).isEqualTo(INVISIBLE)
assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
+ // wait for the show bubble animation to complete
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
DynamicAnimation.ALPHA,
- DynamicAnimation.TRANSLATION_Y
+ DynamicAnimation.TRANSLATION_Y,
+ DynamicAnimation.SCALE_Y,
)
assertThat(bubbleView.alpha).isEqualTo(1)
assertThat(bubbleView.translationY).isEqualTo(-50)
+ assertThat(bubbleView.scaleY).isEqualTo(1)
+ val showHandleAnimator = AnimatorSet()
+ showHandleAnimator.duration = 0
+ whenever(bubbleStashController.buildShowHandleAnimationForNewBubble())
+ .thenReturn(showHandleAnimator)
+ var showHandleAnimationStarted = false
+ showHandleAnimator.doOnStart { showHandleAnimationStarted = true }
+
+ // execute the hide bubble animation
assertThat(animatorScheduler.delayedBlock).isNotNull()
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+ // finish the hide bubble animation
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animatorTestRule.advanceTimeBy(250)
+ }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
- DynamicAnimation.ALPHA,
- DynamicAnimation.TRANSLATION_Y
- )
+ assertThat(showHandleAnimationStarted).isTrue()
assertThat(bubbleView.alpha).isEqualTo(1)
assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
@@ -125,6 +162,16 @@
assertThat(overflowView.visibility).isEqualTo(VISIBLE)
}
+ private fun AnimatorSet.doOnStart(onStart: () -> Unit) {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animator: Animator) {
+ onStart()
+ }
+ }
+ )
+ }
+
private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
var delayedBlock: (() -> Unit)? = null
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 18b1ea0..a7ed8a7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
import java.util.function.Consumer
@@ -65,6 +66,7 @@
private val context: StatefulActivity<*> = mock()
private val recentsModel: RecentsModel = mock()
private val pendingIntent: PendingIntent = mock()
+ private val splitFromDesktopController: SplitFromDesktopController = mock()
private lateinit var splitSelectStateController: SplitSelectStateController
@@ -607,6 +609,18 @@
assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
}
+ @Test
+ fun splitSelectStateControllerDestroyed_SplitFromDesktopControllerAlsoDestroyed() {
+ // Initiate split from desktop controller
+ splitSelectStateController.initSplitFromDesktopController(splitFromDesktopController)
+
+ // Simulate default controller being destroyed
+ splitSelectStateController.onDestroy()
+
+ // Verify desktop controller is also destroyed
+ verify(splitFromDesktopController).onDestroy()
+ }
+
// Generate GroupTask with default userId.
private fun generateGroupTask(
task1ComponentName: ComponentName,
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
new file mode 100644
index 0000000..5c7b4ab
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.app.prediction.AppTarget
+import android.app.prediction.AppTargetEvent
+import android.app.prediction.AppTargetId
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Context
+import android.os.Process.myUserHandle
+import android.os.UserHandle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession
+import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions
+import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Predicate
+import junit.framework.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+class WidgetsPredictionsRequesterTest {
+
+ private lateinit var mUserHandle: UserHandle
+ private lateinit var context: Context
+ private lateinit var deviceProfile: DeviceProfile
+ private lateinit var testInvariantProfile: InvariantDeviceProfile
+
+ private lateinit var widget1aInfo: AppWidgetProviderInfo
+ private lateinit var widget1bInfo: AppWidgetProviderInfo
+ private lateinit var widget2Info: AppWidgetProviderInfo
+
+ private lateinit var widgetItem1a: WidgetItem
+ private lateinit var widgetItem1b: WidgetItem
+ private lateinit var widgetItem2: WidgetItem
+
+ private lateinit var allWidgets: Map<PackageUserKey, List<WidgetItem>>
+
+ @Mock private lateinit var iconCache: IconCache
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mUserHandle = myUserHandle()
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ testInvariantProfile = LauncherAppState.getIDP(context)
+ deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context)
+
+ widget1aInfo =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_A_CLASS_NAME)
+ )
+ widget1bInfo =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_B_CLASS_NAME)
+ )
+ widgetItem1a = createWidgetItem(widget1aInfo)
+ widgetItem1b = createWidgetItem(widget1bInfo)
+
+ widget2Info =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)
+ )
+ widgetItem2 = createWidgetItem(widget2Info)
+
+ allWidgets =
+ mapOf(
+ PackageUserKey(APP_1_PACKAGE_NAME, mUserHandle) to
+ listOf(widgetItem1a, widgetItem1b),
+ PackageUserKey(APP_2_PACKAGE_NAME, mUserHandle) to listOf(widgetItem2),
+ )
+ }
+
+ @Test
+ fun buildBundleForPredictionSession_includesAddedAppWidgets() {
+ val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info)
+
+ val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE)
+ val addedWidgetsBundleExtra =
+ bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java)
+
+ assertNotNull(addedWidgetsBundleExtra)
+ assertThat(addedWidgetsBundleExtra)
+ .containsExactly(
+ buildExpectedAppTargetEvent(
+ /*pkg=*/ APP_1_PACKAGE_NAME,
+ /*providerClassName=*/ APP_1_PROVIDER_A_CLASS_NAME,
+ /*user=*/ mUserHandle
+ ),
+ buildExpectedAppTargetEvent(
+ /*pkg=*/ APP_1_PACKAGE_NAME,
+ /*providerClassName=*/ APP_1_PROVIDER_B_CLASS_NAME,
+ /*user=*/ mUserHandle
+ ),
+ buildExpectedAppTargetEvent(
+ /*pkg=*/ APP_2_PACKAGE_NAME,
+ /*providerClassName=*/ APP_2_PROVIDER_1_CLASS_NAME,
+ /*user=*/ mUserHandle
+ )
+ )
+ }
+
+ @Test
+ fun filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictions() {
+ val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
+ val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
+
+ val predictions =
+ listOf(
+ // already on surface
+ AppTarget(
+ AppTargetId(APP_1_PACKAGE_NAME),
+ APP_1_PACKAGE_NAME,
+ APP_1_PROVIDER_B_CLASS_NAME,
+ mUserHandle
+ ),
+ // eligible
+ AppTarget(
+ AppTargetId(APP_2_PACKAGE_NAME),
+ APP_2_PACKAGE_NAME,
+ APP_2_PROVIDER_1_CLASS_NAME,
+ mUserHandle
+ )
+ )
+
+ // only 2 was eligible
+ assertThat(filterPredictions(predictions, allWidgets, filter)).containsExactly(widgetItem2)
+ }
+
+ @Test
+ fun filterPredictions_appPredictions_returnsWidgetFromPackage() {
+ val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
+ val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
+
+ val predictions =
+ listOf(
+ AppTarget(
+ AppTargetId(APP_1_PACKAGE_NAME),
+ APP_1_PACKAGE_NAME,
+ "$APP_1_PACKAGE_NAME.SomeActivity",
+ mUserHandle
+ ),
+ AppTarget(
+ AppTargetId(APP_2_PACKAGE_NAME),
+ APP_2_PACKAGE_NAME,
+ "$APP_2_PACKAGE_NAME.SomeActivity2",
+ mUserHandle
+ ),
+ )
+
+ assertThat(filterPredictions(predictions, allWidgets, filter))
+ .containsExactly(widgetItem1a, widgetItem2)
+ }
+
+ private fun createWidgetItem(
+ providerInfo: AppWidgetProviderInfo,
+ ): WidgetItem {
+ val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
+ return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
+ }
+
+ companion object {
+ const val TEST_UI_SURFACE = "widgets_test"
+ const val BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets"
+
+ const val APP_1_PACKAGE_NAME = "com.example.app1"
+ const val APP_1_PROVIDER_A_CLASS_NAME = "app1Provider1"
+ const val APP_1_PROVIDER_B_CLASS_NAME = "app1Provider2"
+
+ const val APP_2_PACKAGE_NAME = "com.example.app2"
+ const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1"
+
+ const val TEST_PACKAGE = "pkg"
+
+ private fun buildExpectedAppTargetEvent(
+ pkg: String,
+ providerClassName: String,
+ userHandle: UserHandle
+ ): AppTargetEvent {
+ val appTarget =
+ AppTarget.Builder(
+ /*id=*/ AppTargetId("widget:$pkg"),
+ /*packageName=*/ pkg,
+ /*user=*/ userHandle
+ )
+ .setClassName(providerClassName)
+ .build()
+ return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
+ .setLaunchLocation(TEST_UI_SURFACE)
+ .build()
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
index df73e09..a9ff161 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
import android.graphics.Rect;
@@ -23,6 +25,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
@@ -45,6 +48,7 @@
@Test
@NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON)
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testThreeButtonsTaskbarBoundsAfterConfigChangeDuringIme() {
Rect taskbarBoundsBefore = getTaskbar().getVisibleBounds();
// Go home and to an IME activity (any configuration change would do, as long as it
diff --git a/res/color-night-v31/material_color_surface_container_high.xml b/res/color-night-v31/material_color_surface_container_high.xml
index 002b88e..edd36fc 100644
--- a/res/color-night-v31/material_color_surface_container_high.xml
+++ b/res/color-night-v31/material_color_surface_container_high.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="17" />
</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_highest.xml b/res/color-night-v31/material_color_surface_container_highest.xml
index 002b88e..e54f953 100644
--- a/res/color-night-v31/material_color_surface_container_highest.xml
+++ b/res/color-night-v31/material_color_surface_container_highest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="22" />
</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_low.xml b/res/color-night-v31/material_color_surface_container_low.xml
index 002b88e..40f0d4c 100644
--- a/res/color-night-v31/material_color_surface_container_low.xml
+++ b/res/color-night-v31/material_color_surface_container_low.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="10" />
</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_lowest.xml b/res/color-night-v31/material_color_surface_container_lowest.xml
index 002b88e..24f559b 100644
--- a/res/color-night-v31/material_color_surface_container_lowest.xml
+++ b/res/color-night-v31/material_color_surface_container_lowest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_high.xml b/res/color-v31/material_color_surface_container_high.xml
index b031c08..a996d51 100644
--- a/res/color-v31/material_color_surface_container_high.xml
+++ b/res/color-v31/material_color_surface_container_high.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="92" />
</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_highest.xml b/res/color-v31/material_color_surface_container_highest.xml
index b031c08..e7a535a 100644
--- a/res/color-v31/material_color_surface_container_highest.xml
+++ b/res/color-v31/material_color_surface_container_highest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="90" />
</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_low.xml b/res/color-v31/material_color_surface_container_low.xml
index b031c08..b8fe01e 100644
--- a/res/color-v31/material_color_surface_container_low.xml
+++ b/res/color-v31/material_color_surface_container_low.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="96" />
</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_lowest.xml b/res/color-v31/material_color_surface_container_lowest.xml
index 674fc73..25e8666 100644
--- a/res/color-v31/material_color_surface_container_lowest.xml
+++ b/res/color-v31/material_color_surface_container_lowest.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="100" />
</selector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_header.xml b/res/drawable/bg_ps_header.xml
index 526bb5a..da31445 100644
--- a/res/drawable/bg_ps_header.xml
+++ b/res/drawable/bg_ps_header.xml
@@ -14,9 +14,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="@dimen/ps_container_corner_radius" />
- <solid android:color="?attr/materialColorSurfaceContainerHigh" />
-</shape>
\ No newline at end of file
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/accent_ripple_color">
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/ps_container_corner_radius" />
+ <solid android:color="?attr/materialColorSurfaceContainerHigh" />
+ </shape>
+ </item>
+</ripple>
diff --git a/res/drawable/ps_lock_background.xml b/res/drawable/ps_lock_background.xml
index b81c23f..0be83db 100644
--- a/res/drawable/ps_lock_background.xml
+++ b/res/drawable/ps_lock_background.xml
@@ -15,13 +15,17 @@
~ limitations under the License.
-->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="4dp">
- <shape android:shape="rectangle">
- <corners android:radius="@dimen/ps_lock_corner_radius" />
- <solid android:color="?attr/materialColorPrimaryFixedDim" />
- <padding
- android:left="@dimen/ps_lock_button_background_padding"
- android:right="@dimen/ps_lock_button_background_padding" />
- </shape>
-</inset>
\ No newline at end of file
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/accent_ripple_color">
+ <item>
+ <inset android:inset="4dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/ps_lock_corner_radius" />
+ <solid android:color="?attr/materialColorPrimaryFixedDim" />
+ <padding
+ android:left="@dimen/ps_lock_button_background_padding"
+ android:right="@dimen/ps_lock_button_background_padding" />
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/res/layout/folder_app_pair.xml b/res/layout/folder_app_pair.xml
new file mode 100644
index 0000000..acecd46
--- /dev/null
+++ b/res/layout/folder_app_pair.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.launcher3.apppairs.AppPairIcon
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:focusable="true"
+ launcher:iconDisplay="folder" >
+ <com.android.launcher3.apppairs.AppPairIconGraphic
+ android:id="@+id/app_pair_icon_graphic"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false" />
+ <com.android.launcher3.BubbleTextView
+ style="@style/BaseIcon"
+ android:id="@+id/app_pair_icon_name"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false"
+ android:layout_gravity="top"
+ android:textColor="?attr/folderTextColor"
+ launcher:iconDisplay="folder" />
+</com.android.launcher3.apppairs.AppPairIcon>
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 106c5b7..12453a5 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -35,14 +35,6 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:layout_gravity="fill"/>
-
- <ImageView
- android:id="@+id/widget_badge"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:importantForAccessibility="no"
- android:layout_gravity="end|bottom"
- android:layout_margin="@dimen/profile_badge_margin"/>
</com.android.launcher3.widget.WidgetCellPreview>
<FrameLayout
@@ -51,44 +43,45 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
android:id="@+id/widget_text_container"
android:orientation="vertical">
<!-- The name of the widget. -->
- <TextView
- android:id="@+id/widget_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:layout_gravity="center_horizontal"
- android:gravity="center_horizontal|center_vertical"
- android:singleLine="true"
- android:maxLines="1"
- android:textColor="?android:attr/textColorPrimary"
- android:drawablePadding="@dimen/widget_cell_app_icon_padding"
- android:textSize="@dimen/widget_cell_font_size" />
-
- <!-- The original dimensions of the widget -->
<TextView
- android:id="@+id/widget_dims"
- android:layout_width="match_parent"
+ android:id="@+id/widget_name"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="@dimen/widget_cell_font_size"
- android:alpha="0.7" />
-
- <TextView
- android:id="@+id/widget_description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textSize="@dimen/widget_cell_font_size"
- android:textColor="?android:attr/textColorSecondary"
- android:maxLines="2"
android:ellipsize="end"
android:fadingEdge="horizontal"
- android:alpha="0.7" />
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal|center_vertical"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:textColor="?android:attr/textColorPrimary"
+ android:drawablePadding="@dimen/widget_cell_app_icon_padding"
+ android:textSize="@dimen/widget_cell_font_size" />
+
+ <!-- The original dimensions of the widget -->
+ <TextView
+ android:id="@+id/widget_dims"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="@dimen/widget_cell_font_size"
+ android:alpha="0.7" />
+
+ <TextView
+ android:id="@+id/widget_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textSize="@dimen/widget_cell_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="2"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:alpha="0.7" />
</LinearLayout>
<Button
@@ -97,8 +90,6 @@
android:layout_height="@dimen/widget_cell_add_button_height"
android:layout_gravity="center"
android:minWidth="0dp"
- android:paddingTop="@dimen/widget_cell_add_button_vertical_padding"
- android:paddingBottom="@dimen/widget_cell_add_button_vertical_padding"
android:paddingStart="@dimen/widget_cell_add_button_start_padding"
android:paddingEnd="@dimen/widget_cell_add_button_end_padding"
android:text="@string/widget_add_button_label"
@@ -106,7 +97,7 @@
android:textSize="@dimen/widget_cell_font_size"
android:gravity="center"
android:visibility="gone"
- android:drawableLeft="@drawable/ic_plus"
+ android:drawableStart="@drawable/ic_plus"
android:drawablePadding="8dp"
android:drawableTint="?attr/widgetPickerAddButtonTextColor"
android:background="@drawable/widget_cell_add_button_background" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index ffc8bd0..b3a25c0 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -52,6 +52,7 @@
<attr name="folderIconBorderColor" format="color" />
<attr name="folderTextColor" format="color" />
<attr name="folderHintTextColor" format="color" />
+ <attr name="appPairSurfaceInFolder" format="color" />
<attr name="isFolderDarkText" format="boolean" />
<attr name="workspaceAccentColor" format="color" />
<attr name="workspaceSurfaceColor" format="color" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 048d6cc..599584b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -17,38 +17,23 @@
<!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
- <integer name="extracted_color_gradient_alpha">153</integer>
-
<!-- A string pointer to the original app name string. This allows derived projects to
easily override the app name without providing all translations -->
<string name="derived_app_name" translatable="false">@string/app_name</string>
- <!-- String representing the intent for search on the apps market. To specify a query, add
- q=<query> to the data to the intent -->
- <string name="market_search_intent" translatable="false">market://search?c=apps</string>
-
<!-- String representing the intent to delete a package.-->
<string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string>
<!-- String representing the fragment class for settings activity.-->
<string name="settings_fragment_name" translatable="false">com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment</string>
- <!-- DragController -->
- <item type="id" name="drag_event_parity" />
-
<!-- AllApps & Launcher transitions -->
- <!-- The duration of the animation from search hint to text entry -->
- <integer name="config_searchHintAnimationDuration">50</integer>
-
<!-- The duration of the PagedView page snap animation -->
<integer name="config_pageSnapAnimationDuration">750</integer>
<!-- The duration of the PagedView page snap animation -->
<integer name="config_keyboardTaskFocusSnapAnimationDuration">750</integer>
- <!-- View tag key used to store SpringAnimation data. -->
- <item type="id" name="spring_animation_tag" />
-
<!-- View tag key used to determine if we should fade in the child views.. -->
<string name="popup_container_iterate_children" translatable="false">popup_container_iterate_children</string>
@@ -91,71 +76,27 @@
<string name="taskbar_model_callbacks_factory_class" translatable="false"></string>
<string name="taskbar_view_callbacks_factory_class" translatable="false"></string>
<string name="launcher_restore_event_logger_class" translatable="false"></string>
-
- <!-- View ID to use for QSB widget -->
- <item type="id" name="qsb_widget" />
-
- <!-- View ID used by cell layout to jail its content -->
- <item type="id" name="cell_layout_jail_id" />
-
- <!-- View IDs to store item highlight information -->
- <item type="id" name="view_unhighlight_background" />
-
- <!-- view ID used to restore work tab state -->
- <item type="id" name="work_tab_state_id" />
-
- <!-- Menu id for feature flags -->
- <item type="id" name="menu_apply_flags" />
+ <!-- Used for determining category of a widget presented in widget recommendations. -->
+ <string name="widget_recommendation_category_provider_class" translatable="false"></string>
<!-- Default packages -->
<string name="wallpaper_picker_package" translatable="false"></string>
<string name="local_colors_extraction_class" translatable="false"></string>
<string name="search_session_manager_class" translatable="false"></string>
- <!-- Accessibility actions -->
- <item type="id" name="action_remove" />
- <item type="id" name="action_uninstall" />
- <item type="id" name="action_reconfigure" />
- <item type="id" name="action_add_to_workspace" />
- <item type="id" name="action_move" />
- <item type="id" name="action_move_to_workspace" />
- <item type="id" name="action_move_screen_backwards" />
- <item type="id" name="action_move_screen_forwards" />
- <item type="id" name="action_resize" />
- <item type="id" name="action_deep_shortcuts" />
- <item type="id" name="action_remote_action_shortcut" />
- <item type="id" name="action_dismiss_prediction" />
- <item type="id" name="action_pin_prediction"/>
-
- <!-- QSB IDs. DO not change -->
- <item type="id" name="search_container_workspace" />
- <item type="id" name="search_container_all_apps" />
- <item type="id" name="search_container_hotseat" />
<!-- Scalable Grid configuration -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
<dimen name="hotseat_bar_bottom_space_default">48</dimen>
<dimen name="hotseat_qsb_space_default">0</dimen>
- <!-- Recents -->
- <item type="id" name="overview_panel"/>
-
<!-- Whether to enable background preloading of task thumbnails. -->
<bool name="config_enableTaskSnapshotPreloading">true</bool>
<!-- Configuration resources -->
- <item name="all_apps_spring_damping_ratio" type="dimen" format="float">0.75</item>
- <item name="all_apps_spring_stiffness" type="dimen" format="float">600</item>
-
<item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.73</item>
<item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">800</item>
- <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.73</item>
- <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">800</item>
-
- <item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.8</item>
- <item name="horizontal_spring_stiffness" type="dimen" format="float">250</item>
-
<item name="swipe_up_rect_scale_damping_ratio" type="dimen" format="float">0.75</item>
<item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
<item name="swipe_up_rect_scale_higher_stiffness" type="dimen" format="float">400</item>
@@ -217,9 +158,6 @@
<!-- Widget component names to be included in fitness category of widget suggestions. -->
<string-array name="fitness_recommendations"></string-array>
- <!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
- <string name="color_generator_class" translatable="false"/>
-
<!-- Swipe back to home related -->
<dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
<dimen name="swipe_back_window_corner_radius">40dp</dimen>
@@ -275,9 +213,6 @@
<!-- Used for custom widgets -->
<array name="custom_widget_providers"/>
- <!-- Used for determining category of a widget presented in widget recommendations. -->
- <string name="widget_recommendation_category_provider_class" translatable="false"></string>
-
<!-- Embed parameters -->
<dimen name="activity_split_ratio" format="float">0.5</dimen>
<integer name="min_width_split">720</integer>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8c58931..e9f8f38 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -183,7 +183,6 @@
<dimen name="widget_cell_add_button_height">48dp</dimen>
<dimen name="widget_cell_add_button_start_padding">8dp</dimen>
<dimen name="widget_cell_add_button_end_padding">16dp</dimen>
- <dimen name="widget_cell_add_button_vertical_padding">10dp</dimen>
<dimen name="widget_tabs_button_horizontal_padding">4dp</dimen>
<dimen name="widget_tabs_horizontal_padding">16dp</dimen>
@@ -277,7 +276,6 @@
<!-- Sizes for managed profile badges -->
<dimen name="profile_badge_size">24dp</dimen>
- <dimen name="profile_badge_margin">5dp</dimen>
<dimen name="profile_badge_minimum_top">2dp</dimen>
<!-- Shadows and outlines -->
@@ -498,6 +496,7 @@
<dimen name="ps_button_width">40dp</dimen>
<dimen name="ps_lock_button_width">89dp</dimen>
<dimen name="ps_app_divider_padding">16dp</dimen>
+ <dimen name="ps_extra_bottom_padding">16dp</dimen>
<dimen name="ps_lock_corner_radius">20dp</dimen>
<dimen name="ps_lock_icon_size">20dp</dimen>
<dimen name="ps_lock_icon_margin_top">10dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 59813ad..7bb9396 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -23,6 +23,40 @@
<item type="id" name="split_topLeft_appInfo" />
<item type="id" name="split_bottomRight_appInfo" />
+ <!-- Accessibility actions -->
+ <item type="id" name="action_remove" />
+ <item type="id" name="action_uninstall" />
+ <item type="id" name="action_reconfigure" />
+ <item type="id" name="action_add_to_workspace" />
+ <item type="id" name="action_move" />
+ <item type="id" name="action_move_to_workspace" />
+ <item type="id" name="action_move_screen_backwards" />
+ <item type="id" name="action_move_screen_forwards" />
+ <item type="id" name="action_resize" />
+ <item type="id" name="action_deep_shortcuts" />
+ <item type="id" name="action_remote_action_shortcut" />
+ <item type="id" name="action_dismiss_prediction" />
+ <item type="id" name="action_pin_prediction"/>
+
+ <!-- QSB IDs. DO not change -->
+ <item type="id" name="search_container_workspace" />
+ <item type="id" name="search_container_all_apps" />
+ <item type="id" name="search_container_hotseat" />
+
+ <!-- View ID to use for QSB widget -->
+ <item type="id" name="qsb_widget" />
+
+ <!-- View ID used by cell layout to jail its content -->
+ <item type="id" name="cell_layout_jail_id" />
+
+ <!-- View IDs to store item highlight information -->
+ <item type="id" name="view_unhighlight_background" />
+
+ <!-- view ID used to restore work tab state -->
+ <item type="id" name="work_tab_state_id" />
+
+ <!-- Menu id for feature flags -->
+ <item type="id" name="menu_apply_flags" />
<!-- Do not change, must be kept in sync with sysui navbar button IDs for tests! -->
<item type="id" name="home" />
@@ -53,4 +87,13 @@
<item type="id" name="ps_settings_button" />
<item type="id" name="ps_transition_image" />
+ <!-- Recents -->
+ <item type="id" name="overview_panel"/>
+
+ <!-- DragController -->
+ <item type="id" name="drag_event_parity" />
+
+ <!-- View tag key used to store SpringAnimation data. -->
+ <item type="id" name="spring_animation_tag" />
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c442195..e1c7d64 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -50,6 +50,8 @@
<string name="app_pair_unlaunchable_at_screen_size">This app pair isn\'t supported on this device</string>
<!-- Displayed when an app pair can't launch at this screen size, but user can unfold device to restore functionality [CHAR_LIMIT=none] -->
<string name="app_pair_needs_unfold">Unfold device to use this app pair</string>
+ <!-- Displayed when user selects a shortcut for an app pair that is currently not available [CHAR_LIMIT=none]-->
+ <string name="app_pair_not_available">App pair isn\'t available</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index e35d241..00b962e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -61,6 +61,7 @@
<item name="isFolderDarkText">true</item>
<item name="folderTextColor">@color/folder_text_color_light</item>
<item name="folderHintTextColor">@color/folder_hint_text_color_light</item>
+ <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
<item name="loadingIconColor">#CCFFFFFF</item>
<item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
<item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
@@ -172,6 +173,7 @@
<item name="isFolderDarkText">false</item>
<item name="folderTextColor">@color/folder_text_color_dark</item>
<item name="folderHintTextColor">@color/folder_hint_text_color_dark</item>
+ <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
<item name="isMainColorDark">true</item>
<item name="loadingIconColor">#99FFFFFF</item>
<item name="iconOnlyShortcutColor">#B3FFFFFF</item>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 4a277f0..af3fdcc 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -200,6 +200,10 @@
}
public static void showForWidget(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
+ // If widget is not added to view hierarchy, we cannot show resize frame at correct location
+ if (widget.getParent() == null) {
+ return;
+ }
Launcher launcher = Launcher.getLauncher(cellLayout.getContext());
AbstractFloatingView.closeAllOpenViews(launcher);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 4c8ed15..83236d1 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -912,7 +912,7 @@
@Nullable
public PreloadIconDrawable applyProgressLevel() {
if (!(getTag() instanceof ItemInfoWithIcon)
- || !((ItemInfoWithIcon) getTag()).isActiveArchive()) {
+ || ((ItemInfoWithIcon) getTag()).isInactiveArchive()) {
return null;
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2e0f676..bc36336 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -359,6 +359,15 @@
return displayOption.grid.name;
}
+ /**
+ * @deprecated This is a temporary solution because on the backup and restore case we modify the
+ * IDP, this resets it. b/332974074
+ */
+ @Deprecated
+ public void reset(Context context) {
+ initGrid(context, getCurrentGridName(context));
+ }
+
@VisibleForTesting
public static String getDefaultGridName(Context context) {
return new InvariantDeviceProfile().initGrid(context, null);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index cfa8967..3273f27 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -680,7 +680,7 @@
@Override
public void onBackCancelled() {
- mStateManager.getState().onBackCancelled(Launcher.this);
+ Launcher.this.onBackCancelled();
}
};
}
@@ -2086,6 +2086,10 @@
mStateManager.getState().onBackInvoked(this);
}
+ protected void onBackCancelled() {
+ mStateManager.getState().onBackCancelled(this);
+ }
+
protected void onScreenOnChanged(boolean isOn) {
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ce3c55a..0fc3211 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,8 +16,9 @@
package com.android.launcher3;
+import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
@@ -73,6 +74,7 @@
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellInfo;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
@@ -241,6 +243,8 @@
public static final int REORDER_TIMEOUT = 650;
protected final Alarm mReorderAlarm = new Alarm();
private PreviewBackground mFolderCreateBg;
+ /** The underlying view that we are dragging something over. */
+ private View mDragOverView = null;
private FolderIcon mDragOverFolderIcon = null;
private boolean mCreateUserFolderOnDrop = false;
private boolean mAddToExistingFolderOnDrop = false;
@@ -1873,12 +1877,9 @@
return false;
}
- boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) dropOverView.getTag()).container
- != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
- boolean willBecomeShortcut =
- (info.itemType == ITEM_TYPE_APPLICATION ||
- info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
+ boolean aboveShortcut = Folder.willAccept(dropOverView.getTag())
+ && ((ItemInfo) dropOverView.getTag()).container != CONTAINER_HOTSEAT_PREDICTION;
+ boolean willBecomeShortcut = Folder.willAcceptItemType(info.itemType);
return (aboveShortcut && willBecomeShortcut);
}
@@ -1925,12 +1926,12 @@
mCreateUserFolderOnDrop = false;
final int screenId = getCellLayoutId(target);
- boolean aboveShortcut = (v.getTag() instanceof WorkspaceItemInfo);
- boolean willBecomeShortcut = (newView.getTag() instanceof WorkspaceItemInfo);
+ boolean aboveShortcut = Folder.willAccept(v.getTag());
+ boolean willBecomeShortcut = Folder.willAccept(newView.getTag());
if (aboveShortcut && willBecomeShortcut) {
- WorkspaceItemInfo sourceInfo = (WorkspaceItemInfo) newView.getTag();
- WorkspaceItemInfo destInfo = (WorkspaceItemInfo) v.getTag();
+ ItemInfo sourceInfo = (ItemInfo) newView.getTag();
+ ItemInfo destInfo = (ItemInfo) v.getTag();
// if the drag started here, we need to remove it from the workspace
if (!external) {
getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
@@ -2384,6 +2385,11 @@
if (mFolderCreateBg != null) {
mFolderCreateBg.animateToRest();
}
+
+ if (mDragOverView instanceof AppPairIcon api) {
+ api.getIconDrawableArea().onTemporaryContainerChange(null);
+ mDragOverView = null;
+ }
}
private void cleanupAddToFolder() {
@@ -2659,32 +2665,36 @@
return;
}
- final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
+ mDragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
ItemInfo info = dragObject.dragInfo;
- boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
+ boolean userFolderPending = willCreateUserFolder(info, mDragOverView, false);
if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
mFolderCreateBg = new PreviewBackground();
mFolderCreateBg.setup(mLauncher, mLauncher, null,
- dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop());
+ mDragOverView.getMeasuredWidth(), mDragOverView.getPaddingTop());
// The full preview background should appear behind the icon
mFolderCreateBg.isClipping = false;
+ if (mDragOverView instanceof AppPairIcon api) {
+ api.getIconDrawableArea().onTemporaryContainerChange(DISPLAY_FOLDER);
+ }
+
mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]);
mDragTargetLayout.clearDragOutlines();
setDragMode(DRAG_MODE_CREATE_FOLDER);
if (dragObject.stateAnnouncer != null) {
dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
- .getDescriptionForDropOver(dragOverView, getContext()));
+ .getDescriptionForDropOver(mDragOverView, getContext()));
}
return;
}
- boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
+ boolean willAddToFolder = willAddToExistingUserFolder(info, mDragOverView);
if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
- mDragOverFolderIcon = ((FolderIcon) dragOverView);
+ mDragOverFolderIcon = ((FolderIcon) mDragOverView);
mDragOverFolderIcon.onDragEnter(info);
if (mDragTargetLayout != null) {
mDragTargetLayout.clearDragOutlines();
@@ -2693,7 +2703,7 @@
if (dragObject.stateAnnouncer != null) {
dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
- .getDescriptionForDropOver(dragOverView, getContext()));
+ .getDescriptionForDropOver(mDragOverView, getContext()));
}
return;
}
@@ -3314,7 +3324,7 @@
}
} else if (child instanceof FolderIcon) {
FolderInfo folderInfo = (FolderInfo) info;
- List<WorkspaceItemInfo> matches = folderInfo.getContents().stream()
+ List<ItemInfo> matches = folderInfo.getContents().stream()
.filter(matcher)
.collect(Collectors.toList());
if (!matches.isEmpty()) {
@@ -3381,7 +3391,7 @@
FolderInfo fi = (FolderInfo) info;
if (fi.anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : fi.getContents()) {
+ for (ItemInfo si : fi.getContents()) {
folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index bb25b6d..9792300 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -423,11 +423,16 @@
widgetInfo.bindOptions = widgetInfo.getDefaultSizeOptions(mContext);
}
Workspace<?> workspace = mContext.getWorkspace();
- workspace.post(
- () -> workspace.snapToPage(workspace.getPageIndexForScreenId(screenId))
- );
- mContext.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
- screenId, coordinates, info.spanX, info.spanY);
+ workspace.post(() -> {
+ workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
+ workspace.setOnPageTransitionEndCallback(() -> {
+ mContext.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ screenId, coordinates, info.spanX, info.spanY);
+ if (finishCallback != null) {
+ finishCallback.accept(/* success= */ true);
+ }
+ });
+ });
} else if (item instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = ((WorkspaceItemInfo) item).clone();
mContext.getModelWriter().addItemToDatabase(info,
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 52073cc..84d6a6f 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -23,6 +23,7 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.BaseAccessibilityDelegate.DragType;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -117,7 +118,7 @@
return mContext.getString(R.string.item_moved);
} else {
ItemInfo info = (ItemInfo) child.getTag();
- if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
+ if (Folder.willAccept(info)) {
return mContext.getString(R.string.folder_created);
} else if (info instanceof FolderInfo) {
@@ -148,8 +149,8 @@
if (TextUtils.isEmpty(info.title)) {
// Find the first item in the folder.
FolderInfo folder = (FolderInfo) info;
- WorkspaceItemInfo firstItem = null;
- for (WorkspaceItemInfo shortcut : folder.getContents()) {
+ ItemInfo firstItem = null;
+ for (ItemInfo shortcut : folder.getContents()) {
if (firstItem == null || firstItem.rank > shortcut.rank) {
firstItem = shortcut;
}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 4b65b73..c255eb5 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Flags.enableExpandingPauseWorkButton;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
@@ -85,6 +86,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -96,6 +98,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -156,6 +159,7 @@
};
private final Paint mNavBarScrimPaint;
private final int mHeaderProtectionColor;
+ private final int mPrivateSpaceBottomExtraSpace;
private final Path mTmpPath = new Path();
private final RectF mTmpRectF = new RectF();
protected AllAppsPagedView mViewPager;
@@ -218,6 +222,8 @@
this,
mActivityContext.getStatsLogManager(),
UserCache.INSTANCE.get(mActivityContext));
+ mPrivateSpaceBottomExtraSpace = context.getResources().getDimensionPixelSize(
+ R.dimen.ps_extra_bottom_padding);
mAH = Arrays.asList(null, null, null);
mNavBarScrimPaint = new Paint();
mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
@@ -511,7 +517,7 @@
switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN);
// Scroll to bottom
if (mPrivateProfileManager != null) {
- mPrivateProfileManager.scrollForViewToBeVisibleInContainer(
+ mPrivateProfileManager.scrollForHeaderToBeVisibleInContainer(
getActiveAppsRecyclerView(),
getPersonalAppList().getAdapterItems(),
mPrivateProfileManager.getPsHeaderHeight(),
@@ -1366,6 +1372,18 @@
invalidateHeader();
}
+ /**
+ * Set {@link Animator.AnimatorListener} on {@link mAllAppsTransitionController} to observe
+ * animation of backing out of all apps search view to all apps view.
+ */
+ public void setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener) {
+ Preconditions.assertNotNull(mAllAppsTransitionController);
+ if (mAllAppsTransitionController == null) {
+ return;
+ }
+ mAllAppsTransitionController.setAllAppsSearchBackAnimationListener(listener);
+ }
+
public void setScrimView(ScrimView scrimView) {
mScrimView = scrimView;
}
@@ -1564,6 +1582,14 @@
int bottomOffset = 0;
if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
+ } else if (isMain() && mPrivateProfileManager != null) {
+ Optional<AdapterItem> privateSpaceHeaderItem = mAppsList.getAdapterItems()
+ .stream()
+ .filter(item -> item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER)
+ .findFirst();
+ if (privateSpaceHeaderItem.isPresent()) {
+ bottomOffset = mPrivateSpaceBottomExtraSpace;
+ }
}
if (isSearchBarFloating()) {
bottomOffset += mSearchContainer.getHeight();
@@ -1580,5 +1606,9 @@
private boolean isSearch() {
return mType == SEARCH;
}
+
+ private boolean isMain() {
+ return mType == MAIN;
+ }
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 63f6227..a4d1dc1 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -44,6 +44,7 @@
import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
+import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
@@ -167,6 +168,8 @@
private final AnimatedFloat mAllAppScale = new AnimatedFloat(this::onScaleProgressChanged);
private final int mNavScrimFlag;
+ @Nullable private Animator.AnimatorListener mAllAppsSearchBackAnimationListener;
+
private boolean mIsVerticalLayout;
// Animation in this class is controlled by a single variable {@link mProgress}.
@@ -312,11 +315,25 @@
}
}
- /** Animate all apps view to 1f scale. */
+ /** Set {@link Animator.AnimatorListener} for scaling all apps scale to 1 animation. */
+ public void setAllAppsSearchBackAnimationListener(Animator.AnimatorListener listener) {
+ mAllAppsSearchBackAnimationListener = listener;
+ }
+
+ /**
+ * Animate all apps view to 1f scale. This is called when backing (exiting) from all apps
+ * search view to all apps view.
+ */
public void animateAllAppsToNoScale() {
- mAllAppScale.animateToValue(1f)
- .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
- .start();
+ if (mAllAppScale.isAnimating()) {
+ return;
+ }
+ Animator animator = mAllAppScale.animateToValue(1f)
+ .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS);
+ if (mAllAppsSearchBackAnimationListener != null) {
+ animator.addListener(mAllAppsSearchBackAnimationListener);
+ }
+ animator.start();
}
/**
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 2190e1a..3ba1eed 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_LEFT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_RIGHT;
import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import android.content.Context;
import android.view.LayoutInflater;
@@ -40,7 +41,6 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.views.ActivityContext;
@@ -262,6 +262,17 @@
icon.reset();
icon.applyFromApplicationInfo(adapterItem.itemInfo);
icon.setOnFocusChangeListener(mIconFocusListener);
+ PrivateProfileManager privateProfileManager = mApps.getPrivateProfileManager();
+ // Set the alpha of the private space icon to 0 upon expanding the header so the
+ // alpha can animate -> 1.
+ if (icon.getAlpha() == 0 || icon.getAlpha() == 1) {
+ icon.setAlpha(privateProfileManager != null
+ && privateProfileManager.isPrivateSpaceItem(adapterItem)
+ && privateProfileManager.getAnimationScrolling()
+ && privateProfileManager.getAnimate()
+ && privateProfileManager.getCurrentState() == STATE_ENABLED
+ ? 0 : 1);
+ }
break;
}
case VIEW_TYPE_EMPTY_SEARCH: {
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index eb967bc..be120cc 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -40,6 +40,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
@@ -52,6 +53,7 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearSmoothScroller;
@@ -91,13 +93,27 @@
private static final int SETTINGS_OPACITY_DURATION = 160;
private final ActivityAllAppsContainerView<?> mAllApps;
private final Predicate<UserHandle> mPrivateProfileMatcher;
+ private final int mPsHeaderHeight;
+ private final RecyclerView.OnScrollListener mOnIdleScrollListener =
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ mAnimationScrolling = false;
+ }
+ }
+ };
private Set<String> mPreInstalledSystemPackages = new HashSet<>();
private Intent mAppInstallerIntent = new Intent();
private PrivateAppsSectionDecorator mPrivateAppsSectionDecorator;
private boolean mPrivateSpaceSettingsAvailable;
private boolean mIsAnimationRunning;
- private int mHeaderHeight;
private boolean mAnimate;
+ private boolean mAnimationScrolling;
+ private Runnable mOnPSHeaderAdded;
+ @Nullable
+ private RelativeLayout mPSHeader;
public PrivateProfileManager(UserManager userManager,
ActivityAllAppsContainerView<?> allApps,
@@ -107,6 +123,8 @@
mAllApps = allApps;
mPrivateProfileMatcher = (user) -> userCache.getUserInfo(user).isPrivate();
UI_HELPER_EXECUTOR.post(this::initializeInBackgroundThread);
+ mPsHeaderHeight = mAllApps.getContext().getResources().getDimensionPixelSize(
+ R.dimen.ps_header_height);
}
/** Adds Private Space Header to the layout. */
@@ -172,19 +190,26 @@
.get(mAllApps.mActivityContext).getValue(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, 0);
}
- /** Resets the current state of Private Profile, w.r.t. to Launcher. */
+ /**
+ * Resets the current state of Private Profile, w.r.t. to Launcher. The decorator should only
+ * be applied upon expand before animating. When collapsing, reset() will remove the decorator
+ * when animation is not running.
+ */
public void reset() {
int previousState = getCurrentState();
boolean isEnabled = !mAllApps.getAppsStore()
.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED);
int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED;
setCurrentState(updatedState);
- resetPrivateSpaceDecorator(updatedState);
+ if (mPSHeader != null) {
+ mPSHeader.setAlpha(1);
+ }
if (transitioningFromLockedToUnlocked(previousState, updatedState)) {
postUnlock();
} else if (transitioningFromUnlockedToLocked(previousState, updatedState)){
executeLock();
}
+ resetPrivateSpaceDecorator(updatedState);
}
/** Opens the Private Space Settings Page. */
@@ -263,7 +288,7 @@
mainAdapterHolder.mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator);
} else {
// Remove Private Space Decorator from the Recycler view.
- if (mPrivateAppsSectionDecorator != null) {
+ if (mPrivateAppsSectionDecorator != null && !mIsAnimationRunning) {
mainAdapterHolder.mRecyclerView.removeItemDecoration(mPrivateAppsSectionDecorator);
}
}
@@ -289,10 +314,13 @@
/** Collapses the private space before the app list has been updated. */
void executeLock() {
- MAIN_EXECUTOR.execute(this::collapsePrivateSpace);
+ MAIN_EXECUTOR.execute(() -> updatePrivateStateAnimator(false));
}
void setAnimationRunning(boolean isAnimationRunning) {
+ if (!isAnimationRunning) {
+ mAnimate = false;
+ }
mIsAnimationRunning = isAnimationRunning;
}
@@ -325,8 +353,13 @@
/** Add Private Space Header view elements based upon {@link UserProfileState} */
public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) {
+ mPSHeader = parent;
+ if (mOnPSHeaderAdded != null) {
+ MAIN_EXECUTOR.execute(mOnPSHeaderAdded);
+ mOnPSHeaderAdded = null;
+ }
// Set the transition duration for the settings and lock button to animate.
- ViewGroup settingAndLockGroup = parent.findViewById(R.id.settingsAndLockGroup);
+ ViewGroup settingAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
if (mAnimate) {
enableLayoutTransition(settingAndLockGroup);
} else {
@@ -335,16 +368,15 @@
}
//Add quietMode image and action for lock/unlock button
- ViewGroup lockButton =
- parent.findViewById(R.id.ps_lock_unlock_button);
+ ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
assert lockButton != null;
addLockButton(lockButton);
//Trigger lock/unlock action from header.
- addHeaderOnClickListener(parent);
+ addHeaderOnClickListener(mPSHeader);
//Add image and action for private space settings button
- ImageButton settingsButton = parent.findViewById(R.id.ps_settings_button);
+ ImageButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
assert settingsButton != null;
addPrivateSpaceSettingsButton(settingsButton);
@@ -352,7 +384,6 @@
ImageView transitionView = parent.findViewById(R.id.ps_transition_image);
assert transitionView != null;
addTransitionImage(transitionView);
- mHeaderHeight = parent.getHeight();
}
/**
@@ -379,8 +410,10 @@
private void addHeaderOnClickListener(RelativeLayout header) {
if (getCurrentState() == STATE_DISABLED) {
header.setOnClickListener(view -> lockingAction(/* lock */ false));
+ header.setClickable(true);
} else {
header.setOnClickListener(null);
+ header.setClickable(false);
}
}
@@ -436,16 +469,18 @@
smoothScroller.setTargetPosition(i);
RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
if (layoutManager != null) {
- layoutManager.startSmoothScroll(smoothScroller);
+ startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller);
+ currentItem.decorationInfo = null;
}
break;
}
// Make the private space apps gone to "collapse".
- if (currentItem.decorationInfo != null) {
+ if (isPrivateSpaceItem(currentItem)) {
RecyclerView.ViewHolder viewHolder =
allAppsRecyclerView.findViewHolderForAdapterPosition(i);
if (viewHolder != null) {
viewHolder.itemView.setVisibility(GONE);
+ currentItem.decorationInfo = null;
}
}
}
@@ -455,7 +490,7 @@
* Upon expanding, only scroll to the item position in the adapter that allows the header to be
* visible.
*/
- public int scrollForViewToBeVisibleInContainer(
+ public int scrollForHeaderToBeVisibleInContainer(
AllAppsRecyclerView allAppsRecyclerView,
List<BaseAllAppsAdapter.AdapterItem> appListAdapterItems,
int psHeaderHeight,
@@ -495,7 +530,7 @@
smoothScroller.setTargetPosition(itemToScrollTo);
RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
if (layoutManager != null) {
- layoutManager.startSmoothScroll(smoothScroller);
+ startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller);
}
}
return itemToScrollTo;
@@ -531,27 +566,56 @@
return collapseAnim;
}
+ private ValueAnimator animateAlphaOfIcons(boolean isExpanding) {
+ float from = isExpanding ? 0 : 1;
+ float to = isExpanding ? 1 : 0;
+ AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
+ List<BaseAllAppsAdapter.AdapterItem> allAppsAdapterItems =
+ mAllApps.getActiveRecyclerView().getApps().getAdapterItems();
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
+ alphaAnim.setDuration(EXPAND_COLLAPSE_DURATION);
+ alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float newAlpha = (float) valueAnimator.getAnimatedValue();
+ for (int i = 0; i < allAppsAdapterItems.size(); i++) {
+ BaseAllAppsAdapter.AdapterItem currentItem = allAppsAdapterItems.get(i);
+ if (isPrivateSpaceItem(currentItem) &&
+ currentItem.viewType != VIEW_TYPE_PRIVATE_SPACE_HEADER) {
+ RecyclerView.ViewHolder viewHolder =
+ allAppsRecyclerView.findViewHolderForAdapterPosition(i);
+ if (viewHolder != null) {
+ viewHolder.itemView.setAlpha(newAlpha);
+ }
+ }
+ }
+ }
+ });
+ return alphaAnim;
+ }
+
/**
* Using PropertySetter{@link PropertySetter}, we can update the view's attributes within an
* animation. At the moment, collapsing, setting alpha changes, and animating the text is done
* here.
*/
- private void updatePrivateStateAnimator(boolean expand, @Nullable ViewGroup psHeader) {
- if (psHeader == null) {
+ private void updatePrivateStateAnimator(boolean expand) {
+ if (mPSHeader == null) {
+ mOnPSHeaderAdded = () -> updatePrivateStateAnimator(expand);
+ setAnimationRunning(false);
return;
}
- ViewGroup settingsAndLockGroup = psHeader.findViewById(R.id.settingsAndLockGroup);
- ViewGroup lockButton = psHeader.findViewById(R.id.ps_lock_unlock_button);
+ ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
+ ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
if (settingsAndLockGroup.getLayoutTransition() == null) {
// Set a new transition if the current ViewGroup does not already contain one as each
// transition should only happen once when applied.
enableLayoutTransition(settingsAndLockGroup);
}
-
- PropertySetter setter = new AnimatedPropertySetter();
- ImageButton settingsButton = psHeader.findViewById(R.id.ps_settings_button);
- updateSettingsGearAlpha(settingsButton, expand, setter);
- AnimatorSet animatorSet = setter.buildAnim();
+ PropertySetter headerSetter = new AnimatedPropertySetter();
+ ImageButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
+ updateSettingsGearAlpha(settingsButton, expand, headerSetter);
+ AnimatorSet animatorSet = headerSetter.buildAnim();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -562,22 +626,50 @@
});
animatorSet.addListener(forEndCallback(() -> {
setAnimationRunning(false);
- mAnimate = false;
if (!expand) {
// Call onAppsUpdated() because it may be canceled when this animation occurs.
mAllApps.getPersonalAppList().onAppsUpdated();
+ if (isPrivateSpaceHidden()) {
+ // TODO (b/325455879): Figure out if we can avoid this.
+ mAllApps.getActiveRecyclerView().getAdapter().notifyDataSetChanged();
+ }
}
}));
- // Play the collapsing together of the stateAnimator to avoid being unable to scroll to the
- // header. Otherwise the smooth scrolling will scroll higher when played with the state
- // animator.
- if (!expand) {
- animatorSet.playTogether(animateCollapseAnimation());
+ if (expand) {
+ animatorSet.playTogether(animateAlphaOfIcons(true));
+ } else {
+ if (isPrivateSpaceHidden()) {
+ animatorSet.playSequentially(animateAlphaOfIcons(false),
+ animateCollapseAnimation(), fadeOutHeaderAlpha());
+ } else {
+ animatorSet.playSequentially(animateAlphaOfIcons(false),
+ animateCollapseAnimation());
+ }
}
animatorSet.setDuration(EXPAND_COLLAPSE_DURATION);
animatorSet.start();
}
+ /** Fades out the private space container. */
+ private ValueAnimator fadeOutHeaderAlpha() {
+ if (mPSHeader == null) {
+ return new ValueAnimator();
+ }
+ float from = 1;
+ float to = 0;
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
+ alphaAnim.setDuration(EXPAND_COLLAPSE_DURATION);
+ alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ if (mPSHeader != null) {
+ mPSHeader.setAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ }
+ });
+ return alphaAnim;
+ }
+
/** Animates the layout changes when the text of the button becomes visible/gone. */
private void enableLayoutTransition(ViewGroup settingsAndLockGroup) {
LayoutTransition settingsAndLockTransition = new LayoutTransition();
@@ -617,10 +709,9 @@
// Animate the text and settings icon.
DeviceProfile deviceProfile =
ActivityContext.lookupContext(mAllApps.getContext()).getDeviceProfile();
- scrollForViewToBeVisibleInContainer(mainAdapterHolder.mRecyclerView, adapterItems,
+ scrollForHeaderToBeVisibleInContainer(mainAdapterHolder.mRecyclerView, adapterItems,
getPsHeaderHeight(), deviceProfile.allAppsCellHeightPx);
- ViewGroup psHeader = getPsHeader(mainAdapterHolder.mRecyclerView, adapterItems);
- updatePrivateStateAnimator(true, psHeader);
+ updatePrivateStateAnimator(true);
}
}
@@ -635,36 +726,28 @@
});
}
- private void collapsePrivateSpace() {
- AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
- AlphabeticalAppsList<?> appList = allAppsRecyclerView.getApps();
- if (appList == null) {
- return;
- }
- ViewGroup psHeader = getPsHeader(allAppsRecyclerView, appList.getAdapterItems());
- assert psHeader != null;
- updatePrivateStateAnimator(false, psHeader);
+ /** Starts the smooth scroll with the provided smoothScroller and add idle listener. */
+ private void startAnimationScroll(AllAppsRecyclerView allAppsRecyclerView,
+ RecyclerView.LayoutManager layoutManager, RecyclerView.SmoothScroller smoothScroller) {
+ mAnimationScrolling = true;
+ layoutManager.startSmoothScroll(smoothScroller);
+ allAppsRecyclerView.removeOnScrollListener(mOnIdleScrollListener);
+ allAppsRecyclerView.addOnScrollListener(mOnIdleScrollListener);
+ }
+
+ boolean getAnimate() {
+ return mAnimate;
+ }
+
+ boolean getAnimationScrolling() {
+ return mAnimationScrolling;
}
int getPsHeaderHeight() {
- return mHeaderHeight;
+ return mPsHeaderHeight;
}
- /** Get the private space header from the adapter items. */
- @Nullable
- private ViewGroup getPsHeader(AllAppsRecyclerView allAppsRecyclerView,
- List<BaseAllAppsAdapter.AdapterItem> adapterItems){
- ViewGroup psHeader = null;
- for (int i = 0; i < adapterItems.size(); i++) {
- BaseAllAppsAdapter.AdapterItem currentItem = adapterItems.get(i);
- if (currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
- RecyclerView.ViewHolder viewHolder =
- allAppsRecyclerView.findViewHolderForAdapterPosition(i);
- if (viewHolder != null) {
- psHeader = (ViewGroup) viewHolder.itemView;
- }
- }
- }
- return psHeader;
+ boolean isPrivateSpaceItem(BaseAllAppsAdapter.AdapterItem item) {
+ return item.decorationInfo != null;
}
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 9010f82..8e82d89 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -30,11 +30,13 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.views.ActivityContext;
@@ -179,10 +181,22 @@
return mIconGraphic;
}
+ public int getContainer() {
+ return mContainer;
+ }
+
+ /**
+ * Ensures that both app icons in the pair are loaded in high resolution.
+ */
+ public void verifyHighRes() {
+ IconCache iconCache = LauncherAppState.getInstance(getContext()).getIconCache();
+ getInfo().fetchHiResIconsIfNeeded(iconCache);
+ }
+
/**
* Called when WorkspaceItemInfos get updated, and the app pair icon may need to be redrawn.
*/
- public void maybeRedrawForWorkspaceUpdate(Predicate<WorkspaceItemInfo> itemCheck) {
+ public void maybeRedrawForWorkspaceUpdate(Predicate<ItemInfo> itemCheck) {
// If either of the app pair icons return true on the predicate (i.e. in the list of
// updated apps), redraw the icon graphic (icon background and both icons).
if (getInfo().anyMatch(itemCheck)) {
diff --git a/src/com/android/launcher3/apppairs/AppPairIconDrawable.java b/src/com/android/launcher3/apppairs/AppPairIconDrawable.java
index c0ac11a..db83d91 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconDrawable.java
+++ b/src/com/android/launcher3/apppairs/AppPairIconDrawable.java
@@ -32,7 +32,7 @@
* A composed Drawable consisting of the two app pair icons and the background behind them (looks
* like two rectangles).
*/
-class AppPairIconDrawable extends Drawable {
+public class AppPairIconDrawable extends Drawable {
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final AppPairIconDrawingParams mP;
private final FastBitmapDrawable mIcon1;
@@ -102,6 +102,7 @@
}
mIcon2.draw(canvas);
+ canvas.restore();
}
/**
@@ -205,4 +206,14 @@
public void setColorFilter(ColorFilter colorFilter) {
mBackgroundPaint.setColorFilter(colorFilter);
}
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mP.getIconSize();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mP.getIconSize();
+ }
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt b/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt
index 62e5771..45dc013 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconDrawingParams.kt
@@ -20,6 +20,7 @@
import com.android.launcher3.BubbleTextView.DISPLAY_FOLDER
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
+import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
class AppPairIconDrawingParams(val context: Context, container: Int) {
@@ -62,7 +63,7 @@
// The app pair icon appears differently in portrait and landscape.
var isLeftRightSplit: Boolean = true
// The background paint color (based on container).
- val bgColor: Int
+ var bgColor: Int = 0
init {
val activity: ActivityContext = ActivityContext.lookupContext(context)
@@ -77,22 +78,21 @@
innerPadding = iconSize * INNER_PADDING_SCALE
memberIconSize = iconSize * MEMBER_ICON_SCALE
updateOrientation(dp)
- if (container == DISPLAY_FOLDER) {
- val ta =
- context.theme.obtainStyledAttributes(
- intArrayOf(R.attr.materialColorSurfaceContainerLowest)
- )
- bgColor = ta.getColor(0, 0)
- ta.recycle()
- } else {
- val ta = context.theme.obtainStyledAttributes(R.styleable.FolderIconPreview)
- bgColor = ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)
- ta.recycle()
- }
+ updateBgColor(container)
}
/** Checks the device orientation and updates isLeftRightSplit accordingly. */
fun updateOrientation(dp: DeviceProfile) {
isLeftRightSplit = dp.isLeftRightSplit
}
+
+ fun updateBgColor(container: Int) {
+ if (container == DISPLAY_FOLDER) {
+ bgColor = Themes.getAttrColor(context, R.attr.appPairSurfaceInFolder)
+ } else {
+ val ta = context.theme.obtainStyledAttributes(R.styleable.FolderIconPreview)
+ bgColor = ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)
+ ta.recycle()
+ }
+ }
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index a3a1cfc..a974133 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.Gravity
import android.widget.FrameLayout
@@ -27,7 +26,6 @@
import com.android.launcher3.DeviceProfile
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
import com.android.launcher3.icons.BitmapInfo
-import com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter
import com.android.launcher3.model.data.AppPairInfo
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
@@ -52,7 +50,10 @@
* 2) One of the member apps can't be launched due to screen size requirements.
*/
@JvmStatic
- fun composeDrawable(appPairInfo: AppPairInfo, p: AppPairIconDrawingParams): Drawable {
+ fun composeDrawable(
+ appPairInfo: AppPairInfo,
+ p: AppPairIconDrawingParams
+ ): AppPairIconDrawable {
// Generate new icons, using themed flag if needed.
val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0
val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags)
@@ -60,28 +61,17 @@
appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
- val shouldDrawAsDisabled =
- appPairInfo.isDisabled || !appPairInfo.isLaunchable(p.context)
-
- // Set disabled status on icons.
- appIcon1.setIsDisabled(shouldDrawAsDisabled)
- appIcon2.setIsDisabled(shouldDrawAsDisabled)
-
// Create icon drawable.
val fullIconDrawable = AppPairIconDrawable(p, appIcon1, appIcon2)
fullIconDrawable.setBounds(0, 0, p.iconSize, p.iconSize)
- // Set disabled color filter on background paint.
- fullIconDrawable.colorFilter =
- if (shouldDrawAsDisabled) getDisabledColorFilter() else null
-
return fullIconDrawable
}
}
private lateinit var parentIcon: AppPairIcon
private lateinit var drawParams: AppPairIconDrawingParams
- private lateinit var drawable: Drawable
+ lateinit var drawable: AppPairIconDrawable
fun init(icon: AppPairIcon, container: Int) {
parentIcon = icon
@@ -116,10 +106,13 @@
redraw()
}
- /** Updates the icon drawable and redraws it */
- fun redraw() {
- drawable = composeDrawable(parentIcon.info, drawParams)
- invalidate()
+ /**
+ * When the icon is temporary moved to a different colored surface, update the background color.
+ * Calling this method with [null] reverts the icon back to its default color.
+ */
+ fun onTemporaryContainerChange(newContainer: Int?) {
+ drawParams.updateBgColor(newContainer ?: parentIcon.container)
+ redraw()
}
/**
@@ -136,6 +129,12 @@
)
}
+ /** Updates the icon drawable and redraws it */
+ fun redraw() {
+ drawable = composeDrawable(parentIcon.info, drawParams)
+ invalidate()
+ }
+
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
drawable.draw(canvas)
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index b7c9161..e476138 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -132,7 +132,16 @@
public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", ENABLED,
- "Add dev options to customize the LPNH trigger slop and milliseconds");
+ "Add dev options and server side control to customize the LPNH "
+ + "trigger slop and milliseconds");
+
+ public static final BooleanFlag CUSTOM_LPH_THRESHOLDS = getReleaseFlag(331800576,
+ "CUSTOM_LPH_THRESHOLDS", DISABLED,
+ "Server side control to customize LPH timeout and touch slop");
+
+ public static final BooleanFlag OVERRIDE_LPNH_LPH_THRESHOLDS = getReleaseFlag(331799727,
+ "OVERRIDE_LPNH_LPH_THRESHOLDS", DISABLED,
+ "Enable AGSA override for LPNH and LPH timeout and touch slop");
public static final BooleanFlag ANIMATE_LPNH =
getReleaseFlag(308693847, "ANIMATE_LPNH", TEAMFOOD,
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index aa3c5ba..dcc55e6 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -19,6 +19,9 @@
import static android.text.TextUtils.isEmpty;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
@@ -66,14 +69,12 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Alarm;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -166,6 +167,22 @@
private static final Rect sTempRect = new Rect();
private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10;
+ /**
+ * Checks if {@code o} is an {@link ItemInfo} type that can be placed in folders.
+ */
+ public static boolean willAccept(Object o) {
+ return o instanceof ItemInfo info && willAcceptItemType(info.itemType);
+ }
+
+ /**
+ * Checks if {@code itemType} is a type that can be placed in folders.
+ */
+ public static boolean willAcceptItemType(int itemType) {
+ return itemType == ITEM_TYPE_APPLICATION
+ || itemType == ITEM_TYPE_DEEP_SHORTCUT
+ || itemType == ITEM_TYPE_APP_PAIR;
+ }
+
private final Alarm mReorderAlarm = new Alarm(Looper.getMainLooper());
private final Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper());
private final Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper());
@@ -313,9 +330,7 @@
public boolean startDrag(View v, DragOptions options) {
Object tag = v.getTag();
- if (tag instanceof WorkspaceItemInfo) {
- WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
-
+ if (tag instanceof ItemInfo item) {
mEmptyCellRank = item.rank;
mCurrentDragView = v;
@@ -346,14 +361,12 @@
}
mContent.removeItem(mCurrentDragView);
- if (dragObject.dragInfo instanceof WorkspaceItemInfo) {
- mItemsInvalidated = true;
+ mItemsInvalidated = true;
- // We do not want to get events for the item being removed, as they will get handled
- // when the drop completes
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.remove((WorkspaceItemInfo) dragObject.dragInfo, true);
- }
+ // We do not want to get events for the item being removed, as they will get handled
+ // when the drop completes
+ try (SuppressInfoChanges s = new SuppressInfoChanges()) {
+ mInfo.remove(dragObject.dragInfo, true);
}
mDragInProgress = true;
mItemAddedBackToSelfViaIcon = false;
@@ -493,7 +506,7 @@
mInfo = info;
mFromTitle = info.title;
mFromLabelState = info.getFromLabelState();
- ArrayList<WorkspaceItemInfo> children = info.getContents();
+ ArrayList<ItemInfo> children = info.getContents();
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
@@ -626,7 +639,7 @@
// onDropComplete. Perform cleanup once drag-n-drop ends.
mDragController.addDragListener(this);
- ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.getContents());
+ ArrayList<ItemInfo> items = new ArrayList<>(mInfo.getContents());
mEmptyCellRank = items.size();
items.add(null); // Add an empty spot at the end
@@ -647,7 +660,7 @@
* is animated relative to the specified View. If the View is null, no animation
* is played.
*/
- private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
+ private void animateOpen(List<ItemInfo> items, int pageNo) {
if (items == null || items.size() <= 1) {
Log.d(TAG, "Couldn't animate folder open because items is: " + items);
return;
@@ -896,8 +909,7 @@
public boolean acceptDrop(DragObject d) {
final ItemInfo item = d.dragInfo;
final int itemType = item.itemType;
- return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT));
+ return Folder.willAcceptItemType(itemType);
}
public void onDragEnter(DragObject d) {
@@ -1050,7 +1062,7 @@
}
} else {
// The drag failed, we need to return the item to the folder
- WorkspaceItemInfo info = (WorkspaceItemInfo) d.dragInfo;
+ ItemInfo info = d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
? mCurrentDragView : mContent.createNewView(info);
ArrayList<View> views = getIconsInReadingOrder();
@@ -1099,7 +1111,7 @@
ArrayList<ItemInfo> items = new ArrayList<>();
int total = mInfo.getContents().size();
for (int i = 0; i < total; i++) {
- WorkspaceItemInfo itemInfo = mInfo.getContents().get(i);
+ ItemInfo itemInfo = mInfo.getContents().get(i);
if (verifier.updateRankAndPos(itemInfo, i)) {
items.add(itemInfo);
}
@@ -1112,8 +1124,7 @@
Executors.MODEL_EXECUTOR.post(() -> {
FolderNameInfos nameInfos = new FolderNameInfos();
FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
- fnp.getSuggestedFolderName(
- getContext(), mInfo.getContents(), nameInfos);
+ fnp.getSuggestedFolderName(getContext(), mInfo.getAppContents(), nameInfos);
mInfo.suggestedFolderNames = nameInfos;
});
}
@@ -1298,15 +1309,15 @@
d.deferDragViewCleanupPostAnimation = false;
mRearrangeOnClose = true;
} else {
- final WorkspaceItemInfo si;
+ final ItemInfo si;
if (pasiSi != null) {
si = pasiSi;
} else if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy.
si = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(launcher);
} else {
- // WorkspaceItemInfo
- si = (WorkspaceItemInfo) d.dragInfo;
+ // WorkspaceItemInfo or AppPairInfo
+ si = d.dragInfo;
}
View currentDragView;
@@ -1314,7 +1325,7 @@
currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
// Actually move the item in the database if it was an external drag. Call this
- // before creating the view, so that WorkspaceItemInfo is updated appropriately.
+ // before creating the view, so that the ItemInfo is updated appropriately.
mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(
si, mInfo.id, 0, si.cellX, si.cellY);
mIsExternalDrag = false;
@@ -1376,14 +1387,14 @@
// This is used so the item doesn't immediately appear in the folder when added. In one case
// we need to create the illusion that the item isn't added back to the folder yet, to
// to correspond to the animation of the icon back into the folder. This is
- public void hideItem(WorkspaceItemInfo info) {
+ public void hideItem(ItemInfo info) {
View v = getViewForInfo(info);
if (v != null) {
v.setVisibility(INVISIBLE);
}
}
- public void showItem(WorkspaceItemInfo info) {
+ public void showItem(ItemInfo info) {
View v = getViewForInfo(info);
if (v != null) {
v.setVisibility(VISIBLE);
@@ -1391,7 +1402,7 @@
}
@Override
- public void onAdd(WorkspaceItemInfo item, int rank) {
+ public void onAdd(ItemInfo item, int rank) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
verifier.updateRankAndPos(item, rank);
@@ -1406,7 +1417,7 @@
}
@Override
- public void onRemove(List<WorkspaceItemInfo> items) {
+ public void onRemove(List<ItemInfo> items) {
mItemsInvalidated = true;
items.stream().map(this::getViewForInfo).forEach(mContent::removeItem);
if (mState == STATE_ANIMATING) {
@@ -1423,7 +1434,7 @@
}
}
- private View getViewForInfo(final WorkspaceItemInfo item) {
+ private View getViewForInfo(final ItemInfo item) {
return mContent.iterateOverItems((info, view) -> info == item);
}
@@ -1432,6 +1443,11 @@
updateTextViewFocus();
}
+ @Override
+ public void onTitleChanged(CharSequence title) {
+ mFolderName.setText(title);
+ }
+
/**
* Utility methods to iterate over items of the view
*/
@@ -1451,7 +1467,7 @@
return mItemsInReadingOrder;
}
- public List<BubbleTextView> getItemsOnPage(int page) {
+ public List<View> getItemsOnPage(int page) {
ArrayList<View> allItems = getIconsInReadingOrder();
int lastPage = mContent.getPageCount() - 1;
int totalItemsInFolder = allItems.size();
@@ -1463,9 +1479,9 @@
int startIndex = page * itemsPerPage;
int endIndex = Math.min(startIndex + numItemsOnCurrentPage, allItems.size());
- List<BubbleTextView> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
+ List<View> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
for (int i = startIndex; i < endIndex; ++i) {
- itemsOnCurrentPage.add((BubbleTextView) allItems.get(i));
+ itemsOnCurrentPage.add(allItems.get(i));
}
return itemsOnCurrentPage;
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index a91373b..7a2ec97 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -43,6 +43,7 @@
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
@@ -127,7 +128,7 @@
(BaseDragLayer.LayoutParams) mFolder.getLayoutParams();
mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams();
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
- final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
+ final List<View> itemsInPreview = getPreviewIconsOnPage(0);
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
@@ -139,8 +140,8 @@
// Match size/scale of icons in the preview
float previewScale = rule.scaleForItem(itemsInPreview.size());
float previewSize = rule.getIconSize() * previewScale;
- float initialScale = previewSize / itemsInPreview.get(0).getIconSize()
- * scaleRelativeToDragLayer;
+ float baseIconSize = getBubbleTextView(itemsInPreview.get(0)).getIconSize();
+ float initialScale = previewSize / baseIconSize * scaleRelativeToDragLayer;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
mFolder.setPivotX(0);
@@ -198,11 +199,12 @@
// Initialize the Folder items' text.
PropertyResetListener colorResetListener =
new PropertyResetListener<>(TEXT_ALPHA_PROPERTY, 1f);
- for (BubbleTextView icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
+ for (View icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
+ BubbleTextView titleText = getBubbleTextView(icon);
if (mIsOpening) {
- icon.setTextVisibility(false);
+ titleText.setTextVisibility(false);
}
- ObjectAnimator anim = icon.createTextAlphaAnimator(mIsOpening);
+ ObjectAnimator anim = titleText.createTextAlphaAnimator(mIsOpening);
anim.addListener(colorResetListener);
play(a, anim);
}
@@ -339,7 +341,7 @@
/**
* Returns the list of "preview items" on {@param page}.
*/
- private List<BubbleTextView> getPreviewIconsOnPage(int page) {
+ private List<View> getPreviewIconsOnPage(int page) {
return mPreviewVerifier.setFolderInfo(mFolder.mInfo)
.previewItemsForPage(page, mFolder.getIconsInReadingOrder());
}
@@ -351,7 +353,7 @@
int previewItemOffsetX, int previewItemOffsetY) {
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
- final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(
+ final List<View> itemsInPreview = getPreviewIconsOnPage(
isOnFirstPage ? 0 : mFolder.mContent.getCurrentPage());
final int numItemsInPreview = itemsInPreview.size();
final int numItemsInFirstPagePreview = isOnFirstPage
@@ -361,48 +363,49 @@
ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets();
for (int i = 0; i < numItemsInPreview; ++i) {
- final BubbleTextView btv = itemsInPreview.get(i);
- CellLayoutLayoutParams btvLp = (CellLayoutLayoutParams) btv.getLayoutParams();
+ final View v = itemsInPreview.get(i);
+ CellLayoutLayoutParams vLp = (CellLayoutLayoutParams) v.getLayoutParams();
// Calculate the final values in the LayoutParams.
- btvLp.isLockedToGrid = true;
- cwc.setupLp(btv);
+ vLp.isLockedToGrid = true;
+ cwc.setupLp(v);
// Match scale of icons in the preview of the items on the first page.
float previewScale = rule.scaleForItem(numItemsInFirstPagePreview);
float previewSize = rule.getIconSize() * previewScale;
- float iconScale = previewSize / itemsInPreview.get(i).getIconSize();
+ float baseIconSize = getBubbleTextView(v).getIconSize();
+ float iconScale = previewSize / baseIconSize;
final float initialScale = iconScale / folderScale;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
- btv.setScaleX(scale);
- btv.setScaleY(scale);
+ v.setScaleX(scale);
+ v.setScaleY(scale);
// Match positions of the icons in the folder with their positions in the preview
rule.computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, mTmpParams);
// The PreviewLayoutRule assumes that the icon size takes up the entire width so we
// offset by the actual size.
- int iconOffsetX = (int) ((btvLp.width - btv.getIconSize()) * iconScale) / 2;
+ int iconOffsetX = (int) ((vLp.width - baseIconSize) * iconScale) / 2;
final int previewPosX =
(int) ((mTmpParams.transX - iconOffsetX + previewItemOffsetX) / folderScale);
- final float paddingTop = btv.getPaddingTop() * iconScale;
+ final float paddingTop = v.getPaddingTop() * iconScale;
final int previewPosY = (int) ((mTmpParams.transY + previewItemOffsetY - paddingTop)
/ folderScale);
- final float xDistance = previewPosX - btvLp.x;
- final float yDistance = previewPosY - btvLp.y;
+ final float xDistance = previewPosX - vLp.x;
+ final float yDistance = previewPosY - vLp.y;
- Animator translationX = getAnimator(btv, View.TRANSLATION_X, xDistance, 0f);
+ Animator translationX = getAnimator(v, View.TRANSLATION_X, xDistance, 0f);
translationX.setInterpolator(previewItemInterpolator);
play(animatorSet, translationX);
- Animator translationY = getAnimator(btv, View.TRANSLATION_Y, yDistance, 0f);
+ Animator translationY = getAnimator(v, View.TRANSLATION_Y, yDistance, 0f);
translationY.setInterpolator(previewItemInterpolator);
play(animatorSet, translationY);
- Animator scaleAnimator = getAnimator(btv, SCALE_PROPERTY, initialScale, finalScale);
+ Animator scaleAnimator = getAnimator(v, SCALE_PROPERTY, initialScale, finalScale);
scaleAnimator.setInterpolator(previewItemInterpolator);
play(animatorSet, scaleAnimator);
@@ -426,20 +429,20 @@
super.onAnimationStart(animation);
// Necessary to initialize values here because of the start delay.
if (mIsOpening) {
- btv.setTranslationX(xDistance);
- btv.setTranslationY(yDistance);
- btv.setScaleX(initialScale);
- btv.setScaleY(initialScale);
+ v.setTranslationX(xDistance);
+ v.setTranslationY(yDistance);
+ v.setScaleX(initialScale);
+ v.setScaleY(initialScale);
}
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- btv.setTranslationX(0.0f);
- btv.setTranslationY(0.0f);
- btv.setScaleX(1f);
- btv.setScaleY(1f);
+ v.setTranslationX(0.0f);
+ v.setTranslationY(0.0f);
+ v.setScaleX(1f);
+ v.setScaleY(1f);
}
});
}
@@ -482,4 +485,15 @@
? ObjectAnimator.ofArgb(drawable, property, v1, v2)
: ObjectAnimator.ofArgb(drawable, property, v2, v1);
}
+
+ /**
+ * Gets the {@link com.android.launcher3.BubbleTextView} from an icon. In some cases the
+ * BubbleTextView is the whole icon itself, while in others it is contained within the view and
+ * only serves to store the title text.
+ */
+ private BubbleTextView getBubbleTextView(View v) {
+ return v instanceof AppPairIcon
+ ? ((AppPairIcon) v).getTitleTextView()
+ : (BubbleTextView) v;
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 62ce311..4d88b68 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -71,6 +71,7 @@
import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.FolderInfo.FolderListener;
import com.android.launcher3.model.data.FolderInfo.LabelState;
@@ -118,7 +119,7 @@
ClippedFolderIconLayoutRule mPreviewLayoutRule;
private PreviewItemManager mPreviewItemManager;
private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0);
- private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
+ private List<ItemInfo> mCurrentPreviewItems = new ArrayList<>();
boolean mAnimating = false;
@@ -215,7 +216,7 @@
// Keep the notification dot up to date with the sum of all the content's dots.
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : folderInfo.getContents()) {
+ for (ItemInfo si : folderInfo.getContents()) {
folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
}
icon.setDotInfo(folderDotInfo);
@@ -261,20 +262,18 @@
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
- return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
- item != mInfo && !mFolder.isOpen());
+ return (Folder.willAcceptItemType(itemType) && item != mInfo && !mFolder.isOpen());
}
public boolean acceptDrop(ItemInfo dragInfo) {
return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
}
- public void addItem(WorkspaceItemInfo item) {
+ public void addItem(ItemInfo item) {
mInfo.add(item, true);
}
- public void removeItem(WorkspaceItemInfo item, boolean animate) {
+ public void removeItem(ItemInfo item, boolean animate) {
mInfo.remove(item, animate);
}
@@ -287,8 +286,8 @@
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
if (SPRING_LOADING_ENABLED &&
((dragInfo instanceof WorkspaceItemFactory)
- || (dragInfo instanceof WorkspaceItemInfo)
- || (dragInfo instanceof PendingAddShortcutInfo))) {
+ || (dragInfo instanceof PendingAddShortcutInfo)
+ || Folder.willAccept(dragInfo))) {
mOpenAlarm.setAlarm(ON_OPEN_DELAY);
}
}
@@ -303,8 +302,8 @@
return mPreviewItemManager.prepareCreateAnimation(destView);
}
- public void performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView,
- final WorkspaceItemInfo srcInfo, final DragObject d, Rect dstRect,
+ public void performCreateAnimation(final ItemInfo destInfo, final View destView,
+ final ItemInfo srcInfo, final DragObject d, Rect dstRect,
float scaleRelativeToDragLayer) {
final DragView srcView = d.dragView;
prepareCreateAnimation(destView);
@@ -330,7 +329,7 @@
mOpenAlarm.cancelAlarm();
}
- private void onDrop(final WorkspaceItemInfo item, DragObject d, Rect finalRect,
+ private void onDrop(final ItemInfo item, DragObject d, Rect finalRect,
float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
item.cellX = -1;
item.cellY = -1;
@@ -361,7 +360,7 @@
int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
boolean itemAdded = false;
if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
- List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
+ List<ItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
mInfo.add(item, index, false);
mCurrentPreviewItems.clear();
mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
@@ -422,7 +421,7 @@
FolderNameInfos nameInfos = new FolderNameInfos();
Executors.MODEL_EXECUTOR.post(() -> {
d.folderNameProvider.getSuggestedFolderName(
- getContext(), mInfo.getContents(), nameInfos);
+ getContext(), mInfo.getAppContents(), nameInfos);
postDelayed(() -> {
setLabelSuggestion(nameInfos, d.logInstanceId);
invalidate();
@@ -475,15 +474,21 @@
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
- WorkspaceItemInfo item;
+ ItemInfo item;
if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy
item = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(getContext());
} else if (d.dragSource instanceof BaseItemDragListener){
// Came from a different window -- make a copy
- item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
+ if (d.dragInfo instanceof AppPairInfo) {
+ // dragged item is app pair
+ item = new AppPairInfo((AppPairInfo) d.dragInfo);
+ } else {
+ // dragged item is WorkspaceItemInfo
+ item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
+ }
} else {
- item = (WorkspaceItemInfo) d.dragInfo;
+ item = d.dragInfo;
}
mFolder.notifyDrop();
onDrop(item, d, null, 1.0f,
@@ -665,7 +670,7 @@
/**
* Returns the list of items which should be visible in the preview
*/
- public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
+ public List<ItemInfo> getPreviewItemsOnPage(int page) {
return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.getContents());
}
@@ -690,12 +695,12 @@
/**
* Updates the preview items which match the provided condition
*/
- public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+ public void updatePreviewItems(Predicate<ItemInfo> itemCheck) {
mPreviewItemManager.updatePreviewItems(itemCheck);
}
@Override
- public void onAdd(WorkspaceItemInfo item, int rank) {
+ public void onAdd(ItemInfo item, int rank) {
updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
@@ -707,7 +712,7 @@
}
@Override
- public void onRemove(List<WorkspaceItemInfo> items) {
+ public void onRemove(List<ItemInfo> items) {
updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
@@ -718,6 +723,7 @@
requestLayout();
}
+ @Override
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
setContentDescription(getAccessiblityTitle(title));
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index f2bed92..8eaa0dc 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -41,8 +41,10 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
@@ -148,7 +150,7 @@
/**
* Binds items to the layout.
*/
- public void bindItems(List<WorkspaceItemInfo> items) {
+ public void bindItems(List<ItemInfo> items) {
if (mViewsBound) {
unbindItems();
}
@@ -164,8 +166,11 @@
CellLayout page = (CellLayout) getChildAt(i);
ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets();
for (int j = container.getChildCount() - 1; j >= 0; j--) {
- container.getChildAt(j).setVisibility(View.VISIBLE);
- mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j));
+ View iconView = container.getChildAt(j);
+ iconView.setVisibility(View.VISIBLE);
+ if (iconView instanceof BubbleTextView) {
+ mViewCache.recycleView(R.layout.folder_application, iconView);
+ }
}
page.removeAllViews();
mViewCache.recycleView(R.layout.folder_page, page);
@@ -185,7 +190,7 @@
* Creates and adds an icon corresponding to the provided rank
* @return the created icon
*/
- public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) {
+ public View createAndAddViewForRank(ItemInfo item, int rank) {
View icon = createNewView(item);
if (!mViewsBound) {
return icon;
@@ -200,7 +205,7 @@
* Adds the {@param view} to the layout based on {@param rank} and updated the position
* related attributes. It assumes that {@param item} is already attached to the view.
*/
- public void addViewForRank(View view, WorkspaceItemInfo item, int rank) {
+ public void addViewForRank(View view, ItemInfo item, int rank) {
int pageNo = rank / mOrganizer.getMaxItemsPerPage();
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) view.getLayoutParams();
@@ -209,26 +214,36 @@
}
@SuppressLint("InflateParams")
- public View createNewView(WorkspaceItemInfo item) {
+ public View createNewView(ItemInfo item) {
if (item == null) {
return null;
}
- final BubbleTextView textView = mViewCache.getView(
- R.layout.folder_application, getContext(), null);
- textView.applyFromWorkspaceItem(item);
- textView.setOnClickListener(mFolder.mActivityContext.getItemOnClickListener());
- textView.setOnLongClickListener(mFolder);
- textView.setOnFocusChangeListener(mFocusIndicatorHelper);
- CellLayoutLayoutParams lp = (CellLayoutLayoutParams) textView.getLayoutParams();
+
+ final View icon;
+ if (item instanceof AppPairInfo api) {
+ // TODO (b/332607759): Make view cache work with app pair icons
+ icon = AppPairIcon.inflateIcon(R.layout.folder_app_pair, ActivityContext.lookupContext(
+ getContext()), null , api, BubbleTextView.DISPLAY_FOLDER);
+ } else {
+ icon = mViewCache.getView(R.layout.folder_application, getContext(), null);
+ ((BubbleTextView) icon).applyFromWorkspaceItem((WorkspaceItemInfo) item);
+ }
+
+ icon.setOnClickListener(mFolder.mActivityContext.getItemOnClickListener());
+ icon.setOnLongClickListener(mFolder);
+ icon.setOnFocusChangeListener(mFocusIndicatorHelper);
+
+ CellLayoutLayoutParams lp = (CellLayoutLayoutParams) icon.getLayoutParams();
if (lp == null) {
- textView.setLayoutParams(new CellLayoutLayoutParams(
+ icon.setLayoutParams(new CellLayoutLayoutParams(
item.cellX, item.cellY, item.spanX, item.spanY));
} else {
lp.setCellX(item.cellX);
lp.setCellY(item.cellY);
lp.cellHSpan = lp.cellVSpan = 1;
}
- return textView;
+
+ return icon;
}
@Nullable
@@ -497,13 +512,20 @@
if (page != null) {
ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
for (int i = parent.getChildCount() - 1; i >= 0; i--) {
- BubbleTextView icon = ((BubbleTextView) parent.getChildAt(i));
- icon.verifyHighRes();
+ View iconView = parent.getChildAt(i);
+ Drawable d = null;
+ if (iconView instanceof BubbleTextView btv) {
+ btv.verifyHighRes();
+ d = btv.getIcon();
+ } else if (iconView instanceof AppPairIcon api) {
+ api.verifyHighRes();
+ d = api.getIconDrawableArea().getDrawable();
+ }
+
// Set the callback back to the actual icon, in case
// it was captured by the FolderIcon
- Drawable d = icon.getIcon();
if (d != null) {
- d.setCallback(icon);
+ d.setCallback(iconView);
}
}
}
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index 33bcf21..07215c4 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -33,7 +33,7 @@
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -86,7 +86,7 @@
FolderInfo info = folder.mInfo;
if (itemCount <= 1) {
View newIcon = null;
- WorkspaceItemInfo finalItem = null;
+ ItemInfo finalItem = null;
if (itemCount == 1) {
// Move the item from the folder to the workspace, in the position of the
diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
index 58efdc1..0faa1c9 100644
--- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
+++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
@@ -17,7 +17,7 @@
import android.graphics.drawable.Drawable;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.model.data.ItemInfo;
/**
* Manages the parameters used to draw a Folder preview item.
@@ -30,7 +30,7 @@
public FolderPreviewItemAnim anim;
public boolean hidden;
public Drawable drawable;
- public WorkspaceItemInfo item;
+ public ItemInfo item;
PreviewItemDrawingParams(float transX, float transY, float scale) {
this.transX = transX;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 9001a0c..6311638 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -16,6 +16,7 @@
package com.android.launcher3.folder;
+import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
@@ -41,7 +42,12 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Utilities;
+import com.android.launcher3.apppairs.AppPairIcon;
+import com.android.launcher3.apppairs.AppPairIconDrawingParams;
+import com.android.launcher3.apppairs.AppPairIconGraphic;
import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Themes;
@@ -125,7 +131,9 @@
}
Drawable prepareCreateAnimation(final View destView) {
- Drawable animateDrawable = ((BubbleTextView) destView).getIcon();
+ Drawable animateDrawable = destView instanceof AppPairIcon
+ ? ((AppPairIcon) destView).getIconDrawableArea().getDrawable()
+ : ((BubbleTextView) destView).getIcon();
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
mReferenceDrawable = animateDrawable;
@@ -258,7 +266,7 @@
}
void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
- List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
+ List<ItemInfo> items = mIcon.getPreviewItemsOnPage(page);
// We adjust the size of the list to match the number of items in the preview.
while (items.size() < params.size()) {
@@ -328,16 +336,18 @@
mNumOfPrevItems = numOfPrevItemsAux;
}
- void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+ void updatePreviewItems(Predicate<ItemInfo> itemCheck) {
boolean modified = false;
for (PreviewItemDrawingParams param : mFirstPageParams) {
- if (itemCheck.test(param.item)) {
+ if (itemCheck.test(param.item)
+ || (param.item instanceof AppPairInfo api && api.anyMatch(itemCheck))) {
setDrawable(param, param.item);
modified = true;
}
}
for (PreviewItemDrawingParams param : mCurrentPageParams) {
- if (itemCheck.test(param.item)) {
+ if (itemCheck.test(param.item)
+ || (param.item instanceof AppPairInfo api && api.anyMatch(itemCheck))) {
setDrawable(param, param.item);
modified = true;
}
@@ -370,15 +380,14 @@
* @param newItems The list of items in the new preview.
* @param dropped The item that was dropped onto the FolderIcon.
*/
- public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems,
- WorkspaceItemInfo dropped) {
+ public void onDrop(List<ItemInfo> oldItems, List<ItemInfo> newItems, ItemInfo dropped) {
int numItems = newItems.size();
final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
buildParamsForPage(0, params, false);
// New preview items for items that are moving in (except for the dropped item).
- List<WorkspaceItemInfo> moveIn = new ArrayList<>();
- for (WorkspaceItemInfo newItem : newItems) {
+ List<ItemInfo> moveIn = new ArrayList<>();
+ for (ItemInfo newItem : newItems) {
if (!oldItems.contains(newItem) && !newItem.equals(dropped)) {
moveIn.add(newItem);
}
@@ -401,10 +410,10 @@
}
// Old preview items that need to be moved out.
- List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems);
+ List<ItemInfo> moveOut = new ArrayList<>(oldItems);
moveOut.removeAll(newItems);
for (int i = 0; i < moveOut.size(); ++i) {
- WorkspaceItemInfo item = moveOut.get(i);
+ ItemInfo item = moveOut.get(i);
int oldIndex = oldItems.indexOf(item);
PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
@@ -418,7 +427,7 @@
}
}
- private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item,
+ private void updateTransitionParam(final PreviewItemDrawingParams p, ItemInfo item,
int prevIndex, int newIndex, int numItems) {
setDrawable(p, item);
@@ -431,16 +440,24 @@
}
@VisibleForTesting
- public void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
- if (item.hasPromiseIconUi() || (item.runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
- PreloadIconDrawable drawable = newPendingIcon(mContext, item);
- p.drawable = drawable;
- } else {
- p.drawable = item.newIcon(mContext,
- Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
+ public void setDrawable(PreviewItemDrawingParams p, ItemInfo item) {
+ if (item instanceof WorkspaceItemInfo wii) {
+ if (wii.hasPromiseIconUi() || (wii.runtimeStatusFlags
+ & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
+ PreloadIconDrawable drawable = newPendingIcon(mContext, wii);
+ p.drawable = drawable;
+ } else {
+ p.drawable = wii.newIcon(mContext,
+ Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
+ }
+ p.drawable.setBounds(0, 0, mIconSize, mIconSize);
+ } else if (item instanceof AppPairInfo api) {
+ AppPairIconDrawingParams appPairParams =
+ new AppPairIconDrawingParams(mContext, DISPLAY_FOLDER);
+ p.drawable = AppPairIconGraphic.composeDrawable(api, appPairParams);
+ p.drawable.setBounds(0, 0, mIconSize, mIconSize);
}
- p.drawable.setBounds(0, 0, mIconSize, mIconSize);
+
p.item = item;
// Set the callback to FolderIcon as it is responsible to drawing the icon. The
// callback will be released when the folder is opened.
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 52fb122..e8f8ae2 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.slice.SliceItem;
@@ -53,11 +54,18 @@
public static final int LAUNCHER_STATE_ALLAPPS = 4;
public static final int LAUNCHER_STATE_UNCHANGED = 5;
+ @NonNull
+ protected final Context mContext;
+ @Nullable
+ protected final ActivityContext mActivityContext;
+
+ private KeyboardStateManager mKeyboardStateManager;
private InstanceId mInstanceId;
- protected @Nullable ActivityContext mActivityContext = null;
- protected @Nullable Context mContext = null;
- private KeyboardStateManager mKeyboardStateManager;
+ public StatsLogManager(@NonNull Context context) {
+ mContext = context;
+ mActivityContext = ActivityContext.lookupContextNoThrow(context);
+ }
/**
* Returns event enum based on the two state transition information when swipe
@@ -1194,10 +1202,7 @@
* Creates a new instance of {@link StatsLogManager} based on provided context.
*/
public static StatsLogManager newInstance(Context context) {
- StatsLogManager manager = Overrides.getObject(StatsLogManager.class,
+ return Overrides.getObject(StatsLogManager.class,
context.getApplicationContext(), R.string.stats_log_manager_class);
- manager.mActivityContext = ActivityContext.lookupContextNoThrow(context);
- manager.mContext = context;
- return manager;
}
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 44e45da..d5de4ce 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -44,6 +44,7 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -259,10 +260,15 @@
itemsIdMap.put(item.id, item);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- collections.put(item.id, (CollectionInfo) item);
+ collections.put(item.id, (FolderInfo) item);
workspaceItems.add(item);
break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
+ collections.put(item.id, (AppPairInfo) item);
+ // Fall through here. App pairs are both containers (like folders) and containable
+ // items (can be placed in folders). So we need to add app pairs to the folders
+ // array (above) but also verify the existence of their container, like regular
+ // apps (below).
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
@@ -277,7 +283,7 @@
Log.e(TAG, msg);
}
} else {
- findOrMakeFolder(item.container).add((WorkspaceItemInfo) item);
+ findOrMakeFolder(item.container).add(item);
}
}
break;
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 1deb665..cc20cd1 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -115,7 +115,7 @@
for (ItemInfo info : firstScreenItems) {
if (info instanceof CollectionInfo ci) {
String collectionItemInfoPackage;
- for (ItemInfo collectionItemInfo : cloneOnMainThread(ci.getContents())) {
+ for (ItemInfo collectionItemInfo : cloneOnMainThread(ci.getAppContents())) {
collectionItemInfoPackage = getPackageName(collectionItemInfo);
if (collectionItemInfoPackage != null
&& packages.contains(collectionItemInfoPackage)) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 30cccd5..e0ced83 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -81,7 +81,6 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
@@ -494,9 +493,7 @@
}
appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
- // Fetch hi-res icons if needed.
- appPair.getContents().stream().filter(ItemInfoWithIcon::usingLowResIcon)
- .forEach(member -> mIconCache.getTitleAndIcon(member, false));
+ appPair.fetchHiResIconsIfNeeded(mIconCache);
}
}
@@ -566,12 +563,16 @@
// Ranks are the source of truth for folder items, so cellX and cellY can be
// ignored for now. Database will be updated once user manually modifies folder.
for (int rank = 0; rank < size; ++rank) {
- WorkspaceItemInfo info = folder.getContents().get(rank);
+ ItemInfo info = folder.getContents().get(rank);
info.rank = rank;
- if (info.usingLowResIcon() && info.itemType == Favorites.ITEM_TYPE_APPLICATION
+ if (info instanceof WorkspaceItemInfo wii
+ && wii.usingLowResIcon()
+ && wii.itemType == Favorites.ITEM_TYPE_APPLICATION
&& verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
- mIconCache.getTitleAndIcon(info, false);
+ mIconCache.getTitleAndIcon(wii, false);
+ } else if (info instanceof AppPairInfo api) {
+ api.fetchHiResIconsIfNeeded(mIconCache);
}
}
}
@@ -788,7 +789,7 @@
FolderNameInfos suggestionInfos = new FolderNameInfos();
CollectionInfo info = mBgDataModel.collections.valueAt(i);
if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
- provider.getSuggestedFolderName(mApp.getContext(), fi.getContents(),
+ provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
suggestionInfos);
fi.suggestedFolderNames = suggestionInfos;
}
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 7e7bfb3..8360b14 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -45,28 +45,29 @@
boolean isPrimaryInstance) {
ModelDelegate delegate = Overrides.getObject(
ModelDelegate.class, context, R.string.model_delegate_class);
- delegate.init(context, app, appsList, dataModel, isPrimaryInstance);
+ delegate.init(app, appsList, dataModel, isPrimaryInstance);
return delegate;
}
- protected Context mContext;
+ protected final Context mContext;
protected LauncherAppState mApp;
protected AllAppsList mAppsList;
protected BgDataModel mDataModel;
protected boolean mIsPrimaryInstance;
- public ModelDelegate() { }
+ public ModelDelegate(Context context) {
+ mContext = context;
+ }
/**
* Initializes the object with the given params.
*/
- private void init(Context context, LauncherAppState app, AllAppsList appsList,
+ private void init(LauncherAppState app, AllAppsList appsList,
BgDataModel dataModel, boolean isPrimaryInstance) {
this.mApp = app;
this.mAppsList = appsList;
this.mDataModel = dataModel;
this.mIsPrimaryInstance = isPrimaryInstance;
- this.mContext = context;
}
/** Called periodically to validate and update any data */
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index aa29290..d27be72 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -375,7 +375,7 @@
val folderInfo: FolderInfo = collection
val newAppPair = AppPairInfo()
// Move the placeholder's contents over to the new app pair.
- folderInfo.contents.forEach(newAppPair::add)
+ folderInfo.getContents().forEach(newAppPair::add)
collection = newAppPair
// Remove the placeholder and add the app pair into the data model.
bgDataModel.collections.remove(c.id)
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index 4081316..63c77bb 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -18,11 +18,14 @@
import android.content.Context
import com.android.launcher3.LauncherSettings
+import com.android.launcher3.icons.IconCache
import com.android.launcher3.logger.LauncherAtom
import com.android.launcher3.views.ActivityContext
/** A type of app collection that launches multiple apps into split screen. */
class AppPairInfo() : CollectionInfo() {
+ private var contents: ArrayList<WorkspaceItemInfo> = ArrayList()
+
init {
itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
}
@@ -33,11 +36,28 @@
add(app2)
}
- /** Adds an element to the contents array. */
- override fun add(item: WorkspaceItemInfo) {
+ /** Creates a new AppPairInfo that is a copy of the provided one. */
+ constructor(appPairInfo: AppPairInfo) : this() {
+ contents = appPairInfo.contents.clone() as ArrayList<WorkspaceItemInfo>
+ copyFrom(appPairInfo)
+ }
+
+ /** Adds an element to the contents ArrayList. */
+ override fun add(item: ItemInfo) {
+ if (item !is WorkspaceItemInfo) {
+ throw RuntimeException("tried to add an illegal type into an app pair")
+ }
+
contents.add(item)
}
+ /** Returns the app pair's member apps as an ArrayList of [ItemInfo]. */
+ override fun getContents(): ArrayList<ItemInfo> =
+ ArrayList(contents.stream().map { it as ItemInfo }.toList())
+
+ /** Returns the app pair's member apps as an ArrayList of [WorkspaceItemInfo]. */
+ override fun getAppContents(): ArrayList<WorkspaceItemInfo> = contents
+
/** Returns the first app in the pair. */
fun getFirstApp() = contents[0]
@@ -50,7 +70,16 @@
/** Checks if the app pair is launchable at the current screen size. */
fun isLaunchable(context: Context) =
(ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet ||
- noneMatch { it.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE) }
+ getAppContents().stream().noneMatch {
+ it.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)
+ }
+
+ /** Fetches high-res icons for member apps if needed. */
+ fun fetchHiResIconsIfNeeded(iconCache: IconCache) {
+ getAppContents().stream().filter(ItemInfoWithIcon::usingLowResIcon).forEach { member ->
+ iconCache.getTitleAndIcon(member, false)
+ }
+ }
/** Generates an ItemInfo for logging. */
override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
diff --git a/src/com/android/launcher3/model/data/CollectionInfo.kt b/src/com/android/launcher3/model/data/CollectionInfo.kt
index 2b865a5..4f5e12f 100644
--- a/src/com/android/launcher3/model/data/CollectionInfo.kt
+++ b/src/com/android/launcher3/model/data/CollectionInfo.kt
@@ -22,19 +22,21 @@
import java.util.function.Predicate
abstract class CollectionInfo : ItemInfo() {
- var contents: ArrayList<WorkspaceItemInfo> = ArrayList()
+ /** Adds an ItemInfo to the collection. Throws if given an illegal type. */
+ abstract fun add(item: ItemInfo)
- abstract fun add(item: WorkspaceItemInfo)
+ /** Returns the collection's contents as an ArrayList of [ItemInfo]. */
+ abstract fun getContents(): ArrayList<ItemInfo>
+
+ /**
+ * Returns the collection's contents as an ArrayList of [WorkspaceItemInfo]. Does not include
+ * other collection [ItemInfo]s that are inside this collection; rather, it should collect
+ * *their* contents and adds them to the ArrayList.
+ */
+ abstract fun getAppContents(): ArrayList<WorkspaceItemInfo>
/** Convenience function. Checks contents to see if any match a given predicate. */
- fun anyMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
- return contents.stream().anyMatch(matcher)
- }
-
- /** Convenience function. Returns true if none of the contents match a given predicate. */
- fun noneMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
- return contents.stream().noneMatch(matcher)
- }
+ fun anyMatch(matcher: Predicate<ItemInfo>) = getContents().stream().anyMatch(matcher)
override fun onAddToDatabase(writer: ContentWriter) {
super.onAddToDatabase(writer)
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 1bbb2fe..18d2b85 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -29,6 +29,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.Attribute;
@@ -98,14 +99,20 @@
public FolderNameInfos suggestedFolderNames;
+ /**
+ * The apps and shortcuts
+ */
+ private final ArrayList<ItemInfo> contents = new ArrayList<>();
+
private ArrayList<FolderListener> mListeners = new ArrayList<>();
public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
}
- /** Adds a app or shortcut to the contents array without animation. */
- public void add(@NonNull WorkspaceItemInfo item) {
+ /** Adds a app or shortcut to the contents ArrayList without animation. */
+ @Override
+ public void add(@NonNull ItemInfo item) {
add(item, false /* animate */);
}
@@ -114,14 +121,18 @@
*
* @param item
*/
- public void add(WorkspaceItemInfo item, boolean animate) {
+ public void add(ItemInfo item, boolean animate) {
add(item, getContents().size(), animate);
}
/**
* Add an app or shortcut for a specified rank.
*/
- public void add(WorkspaceItemInfo item, int rank, boolean animate) {
+ public void add(ItemInfo item, int rank, boolean animate) {
+ if (!Folder.willAccept(item)) {
+ throw new RuntimeException("tried to add an illegal type into a folder");
+ }
+
rank = Utilities.boundToRange(rank, 0, getContents().size());
getContents().add(rank, item);
for (int i = 0; i < mListeners.size(); i++) {
@@ -135,21 +146,49 @@
*
* @param item
*/
- public void remove(WorkspaceItemInfo item, boolean animate) {
+ public void remove(ItemInfo item, boolean animate) {
removeAll(Collections.singletonList(item), animate);
}
/**
* Remove all matching app or shortcut. Does not change the DB.
*/
- public void removeAll(List<WorkspaceItemInfo> items, boolean animate) {
- getContents().removeAll(items);
+ public void removeAll(List<ItemInfo> items, boolean animate) {
+ contents.removeAll(items);
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onRemove(items);
}
itemsChanged(animate);
}
+ /**
+ * Returns the folder's contents as an ArrayList of {@link ItemInfo}. Includes
+ * {@link WorkspaceItemInfo} and {@link AppPairInfo}s.
+ */
+ @NonNull
+ @Override
+ public ArrayList<ItemInfo> getContents() {
+ return contents;
+ }
+
+ /**
+ * Returns the folder's contents as an ArrayList of {@link WorkspaceItemInfo}. Note: Does not
+ * return any {@link AppPairInfo}s contained in the folder, instead collects *their* contents
+ * and adds them to the ArrayList.
+ */
+ @Override
+ public ArrayList<WorkspaceItemInfo> getAppContents() {
+ ArrayList<WorkspaceItemInfo> workspaceItemInfos = new ArrayList<>();
+ for (ItemInfo item : contents) {
+ if (item instanceof WorkspaceItemInfo wii) {
+ workspaceItemInfos.add(wii);
+ } else if (item instanceof AppPairInfo api) {
+ workspaceItemInfos.addAll(api.getAppContents());
+ }
+ }
+ return workspaceItemInfos;
+ }
+
@Override
public void onAddToDatabase(@NonNull ContentWriter writer) {
super.onAddToDatabase(writer);
@@ -171,9 +210,11 @@
}
public interface FolderListener {
- void onAdd(WorkspaceItemInfo item, int rank);
- void onRemove(List<WorkspaceItemInfo> item);
+ void onAdd(ItemInfo item, int rank);
+ void onRemove(List<ItemInfo> item);
void onItemsChanged(boolean animate);
+ void onTitleChanged(CharSequence title);
+
}
public boolean hasOption(int optionFlag) {
@@ -246,6 +287,10 @@
if (modelWriter != null) {
modelWriter.updateItemInDatabase(this);
}
+
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onTitleChanged(title);
+ }
}
/**
@@ -263,10 +308,17 @@
public ItemInfo makeShallowCopy() {
FolderInfo folderInfo = new FolderInfo();
folderInfo.copyFrom(this);
- folderInfo.setContents(this.getContents());
return folderInfo;
}
+ @Override
+ public void copyFrom(@NonNull ItemInfo info) {
+ super.copyFrom(info);
+ if (info instanceof FolderInfo fi) {
+ contents.addAll(fi.getContents());
+ }
+ }
+
/**
* Returns index of the accepted suggestion.
*/
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 9fbc6bf..be3aa10 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -168,9 +168,9 @@
return (runtimeStatusFlags & FLAG_ARCHIVED) != 0;
}
- /** Returns true if the app is archived and has an active install session. */
- public boolean isActiveArchive() {
- return isArchived() && (runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0;
+ /** Returns true if the app is archived and doesn't have an active install session. */
+ public boolean isInactiveArchive() {
+ return isArchived() && (runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) == 0;
}
/**
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 9f2b10f..b4e6365 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -126,6 +126,9 @@
if (Flags.enableNarrowGridRestore()) {
String oldPhoneFileName = idp.dbFile;
removeOldDBs(context, oldPhoneFileName);
+ // The idp before this contains data about the old phone, after this it becomes the idp
+ // of the current phone.
+ idp.reset(context);
trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName);
} else {
idp.reinitializeAfterRestore(context);
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 6950fb5..fdb37f0 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -35,6 +35,7 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.util.DisplayController;
@@ -42,6 +43,7 @@
* Utility class to manage launcher rotation
*/
public class RotationHelper implements OnSharedPreferenceChangeListener,
+ DeviceProfile.OnDeviceProfileChangeListener,
DisplayController.DisplayInfoChangeListener {
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
@@ -119,10 +121,24 @@
}
}
+ /**
+ * Listening to both onDisplayInfoChanged and onDeviceProfileChanged to reduce delay. While
+ * onDeviceProfileChanged is triggered earlier, it only receives callback when Launcher is in
+ * the foreground. When in the background, we can still rely on onDisplayInfoChanged to update,
+ * assuming that the delay is tolerable since it takes time to change to foreground.
+ */
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ onIgnoreAutoRotateChanged(info.isTablet(info.realBounds));
+ }
+
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ onIgnoreAutoRotateChanged(dp.isTablet);
+ }
+
+ private void onIgnoreAutoRotateChanged(boolean ignoreAutoRotateSettings) {
if (mDestroyed) return;
- boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds);
if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
notifyChange();
@@ -161,12 +177,14 @@
DisplayController.Info info = displayController.getInfo();
setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
displayController.addChangeListener(this);
+ mActivity.addOnDeviceProfileChangeListener(this);
notifyChange();
}
public void destroy() {
if (mDestroyed) return;
mDestroyed = true;
+ mActivity.removeOnDeviceProfileChangeListener(this);
DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 0c25e96..50df775 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -157,20 +157,27 @@
? R.string.app_pair_needs_unfold
: R.string.app_pair_unlaunchable_at_screen_size,
Toast.LENGTH_SHORT).show();
+ return;
} else if (appPairIcon.getInfo().isDisabled()) {
WorkspaceItemInfo app1 = appPairIcon.getInfo().getFirstApp();
WorkspaceItemInfo app2 = appPairIcon.getInfo().getSecondApp();
// Show the user why the app pair is disabled.
- if (app1.isDisabled() && !handleDisabledItemClicked(app1, launcher)) {
- // If handleDisabledItemClicked() did not handle the error message, we initiate an
- // app launch so Framework can tell the user why the app is suspended.
- onClickAppShortcut(v, app1, launcher);
- } else if (app2.isDisabled() && !handleDisabledItemClicked(app2, launcher)) {
- onClickAppShortcut(v, app2, launcher);
+ if (app1.isDisabled() && app2.isDisabled()) {
+ // Both apps are disabled, show "app pair is not available" toast.
+ Toast.makeText(launcher, R.string.app_pair_not_available, Toast.LENGTH_SHORT)
+ .show();
+ return;
+ } else if ((app1.isDisabled() && handleDisabledItemClicked(app1, launcher))
+ || (app2.isDisabled() && handleDisabledItemClicked(app2, launcher))) {
+ // Only one is disabled, and handleDisabledItemClicked() will show a toast, so we
+ // are done.
+ return;
}
- } else {
- launcher.launchAppPair(appPairIcon);
}
+
+ // Either the app pair is not disabled, or it is a disabled state that can be handled by
+ // framework directly (e.g. one app is paused), so go ahead and launch.
+ launcher.launchAppPair(appPairIcon);
}
/**
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 116f13a..89057a2 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -39,6 +39,7 @@
import com.android.launcher3.folder.Folder;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PrivateSpaceInstallAppButtonInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.views.BubbleTextHolder;
@@ -150,7 +151,10 @@
if (launcher.getWorkspace().isSwitchingState()) return false;
StatsLogger logger = launcher.getStatsLogManager().logger();
- if (v.getTag() instanceof ItemInfo) {
+ if (v.getTag() instanceof ItemInfo itemInfo) {
+ if (itemInfo instanceof PrivateSpaceInstallAppButtonInfo) {
+ return false;
+ }
logger.withItemInfo((ItemInfo) v.getTag());
}
logger.log(LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED);
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.kt b/src/com/android/launcher3/util/OnboardingPrefs.kt
index 4528cba..370b4c8 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.kt
+++ b/src/com/android/launcher3/util/OnboardingPrefs.kt
@@ -75,4 +75,8 @@
@JvmField
val HOTSEAT_LONGPRESS_TIP_SEEN = backedUpItem("launcher.hotseat_longpress_tip_seen", false)
+
+ @JvmField
+ val TASKBAR_CIRCLE_TO_SEARCH_EDU_SEEN =
+ backedUpItem("launcher.taskbar_circle_to_search_edu_seen", false)
}
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index b66b96a..316506a 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -48,7 +48,6 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.ApiWrapper;
-import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
@@ -167,22 +166,6 @@
}
/**
- * Creates a new market search intent.
- */
- public static Intent getMarketSearchIntent(Context context, String query) {
- try {
- Intent intent = Intent.parseUri(context.getString(R.string.market_search_intent), 0);
- if (!TextUtils.isEmpty(query)) {
- intent.setData(
- intent.getData().buildUpon().appendQueryParameter("q", query).build());
- }
- return intent;
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
* Starts the details activity for {@code info}
*/
public void startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts) {
diff --git a/src/com/android/launcher3/util/ResourceBasedOverride.java b/src/com/android/launcher3/util/ResourceBasedOverride.java
index e2c4992..36b9cf7 100644
--- a/src/com/android/launcher3/util/ResourceBasedOverride.java
+++ b/src/com/android/launcher3/util/ResourceBasedOverride.java
@@ -34,16 +34,20 @@
public static <T extends ResourceBasedOverride> T getObject(
Class<T> clazz, Context context, int resId) {
String className = context.getString(resId);
- if (!TextUtils.isEmpty(className)) {
- try {
- Class<?> cls = Class.forName(className);
- return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
- } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
- | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+ boolean isOverridden = !TextUtils.isEmpty(className);
+
+ // First try to load the class with "Context" param
+ try {
+ Class<?> cls = isOverridden ? Class.forName(className) : clazz;
+ return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+ | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
+ if (isOverridden) {
Log.e(TAG, "Bad overriden class", e);
}
}
+ // Load the base class with no parameter
try {
return clazz.newInstance();
} catch (InstantiationException|IllegalAccessException e) {
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 4c9371d..bac3345 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -82,7 +82,6 @@
};
protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
- private static final float VIEW_NO_SCALE = 1f;
private static final int DEFAULT_DURATION = 300;
protected final T mActivityContext;
@@ -129,9 +128,13 @@
protected @Nullable OnCloseListener mOnCloseBeginListener;
protected List<OnCloseListener> mOnCloseListeners = new ArrayList<>();
- protected final AnimatedFloat mSlideInViewScale =
- new AnimatedFloat(this::onScaleProgressChanged, VIEW_NO_SCALE);
- protected boolean mIsBackProgressing;
+ /**
+ * How far through a "user initiated dismissal" the UI is. e.g. Predictive back, swipe to home,
+ * 0 is regular state, 1 is fully dismissed.
+ */
+ protected final AnimatedFloat mSwipeToDismissProgress =
+ new AnimatedFloat(this::onUserSwipeToDismissProgressChanged, 0f);
+ protected boolean mIsDismissInProgress;
private @Nullable Drawable mContentBackground;
private @Nullable View mContentBackgroundParentView;
@@ -287,29 +290,30 @@
final float progress = backEvent.getProgress();
float deceleratedProgress =
Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(progress);
- mIsBackProgressing = progress > 0f;
- mSlideInViewScale.updateValue(PREDICTIVE_BACK_MIN_SCALE
- + (1 - PREDICTIVE_BACK_MIN_SCALE) * (1 - deceleratedProgress));
+ mSwipeToDismissProgress.updateValue(deceleratedProgress);
}
- protected void onScaleProgressChanged() {
- float scaleProgress = mSlideInViewScale.value;
- SCALE_PROPERTY.set(this, scaleProgress);
- setClipChildren(!mIsBackProgressing);
- setClipToPadding(!mIsBackProgressing);
- mContent.setClipChildren(!mIsBackProgressing);
- mContent.setClipToPadding(!mIsBackProgressing);
+ protected void onUserSwipeToDismissProgressChanged() {
+ float progress = mSwipeToDismissProgress.value;
+ mIsDismissInProgress = progress > 0f;
+
+ float scale = PREDICTIVE_BACK_MIN_SCALE + (1 - PREDICTIVE_BACK_MIN_SCALE) * (1f - progress);
+ SCALE_PROPERTY.set(this, scale);
+ setClipChildren(!mIsDismissInProgress);
+ setClipToPadding(!mIsDismissInProgress);
+ mContent.setClipChildren(!mIsDismissInProgress);
+ mContent.setClipToPadding(!mIsDismissInProgress);
invalidate();
}
@Override
public void onBackCancelled() {
super.onBackCancelled();
- animateSlideInViewToNoScale();
+ animateSwipeToDismissProgressToStart();
}
- protected void animateSlideInViewToNoScale() {
- mSlideInViewScale.animateToValue(1f)
+ protected void animateSwipeToDismissProgressToStart() {
+ mSwipeToDismissProgress.animateToValue(0f)
.setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
.start();
}
@@ -340,7 +344,7 @@
mContentBackgroundParentView.getTop() + (int) mContent.getTranslationY(),
mContentBackgroundParentView.getRight(),
mContentBackgroundParentView.getBottom()
- + (mIsBackProgressing ? getBottomOffsetPx() : 0));
+ + (mIsDismissInProgress ? getBottomOffsetPx() : 0));
mContentBackground.draw(canvas);
}
diff --git a/src/com/android/launcher3/views/WidgetsEduView.java b/src/com/android/launcher3/views/WidgetsEduView.java
index 45ff9de..53fbd8f 100644
--- a/src/com/android/launcher3/views/WidgetsEduView.java
+++ b/src/com/android/launcher3/views/WidgetsEduView.java
@@ -70,9 +70,9 @@
}
@Override
- protected void onScaleProgressChanged() {
- super.onScaleProgressChanged();
- setTranslationY(getMeasuredHeight() * (1 - mSlideInViewScale.value) / 2);
+ protected void onUserSwipeToDismissProgressChanged() {
+ super.onUserSwipeToDismissProgressChanged();
+ setTranslationY(getMeasuredHeight() * (mSwipeToDismissProgress.value / 2));
}
private void show() {
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 76ffbbd..c17ae09 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
import static com.android.launcher3.Flags.enableWidgetTapToAdd;
import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_TIP_SEEN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_ADD_BUTTON_TAP;
import android.content.Context;
import android.graphics.Canvas;
@@ -32,6 +33,7 @@
import android.view.WindowInsets;
import android.view.animation.Interpolator;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.core.view.ViewCompat;
@@ -45,7 +47,6 @@
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -55,6 +56,8 @@
import com.android.launcher3.views.AbstractSlideInView;
import com.android.launcher3.views.ArrowTipView;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/**
* Base class for various widgets popup
*/
@@ -163,12 +166,25 @@
/**
* Click handler for tap to add button.
*/
- public void addWidget(PendingAddItemInfo info) {
+ private void addWidget(@NonNull PendingAddItemInfo info) {
+ // Using a boolean flag here to make sure the callback is only run once. This should never
+ // happen because we close the sheet and it will be reconstructed the next time it is
+ // needed.
+ final AtomicBoolean hasRun = new AtomicBoolean(false);
+ addOnCloseListener(() -> {
+ if (!hasRun.get()) {
+ Launcher.getLauncher(mActivityContext).getAccessibilityDelegate().addToWorkspace(
+ info, /*accessibility=*/ false,
+ /*finishCallback=*/ (success) -> {
+ mActivityContext.getStatsLogManager()
+ .logger()
+ .withItemInfo(info)
+ .log(LAUNCHER_WIDGET_ADD_BUTTON_TAP);
+ });
+ hasRun.set(true);
+ }
+ });
handleClose(true);
- Launcher.getLauncher(mActivityContext).getAccessibilityDelegate()
- .addToWorkspace(info, /*accessibility=*/ false, /*finishCallback=*/ null);
- mActivityContext.getStatsLogManager().logger().withItemInfo(info).log(
- StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_ADD_BUTTON_TAP);
}
@Override
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 3dff555..ab007fb 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.os.Process;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -39,7 +38,6 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -88,7 +86,6 @@
private Size mPreviewContainerSize = new Size(0, 0);
private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
- private ImageView mWidgetBadge;
private TextView mWidgetName;
private TextView mWidgetDims;
private TextView mWidgetDescription;
@@ -142,7 +139,6 @@
mWidgetImageContainer = findViewById(R.id.widget_preview_container);
mWidgetImage = findViewById(R.id.widget_preview);
- mWidgetBadge = findViewById(R.id.widget_badge);
mWidgetName = findViewById(R.id.widget_name);
mWidgetDims = findViewById(R.id.widget_dims);
mWidgetDescription = findViewById(R.id.widget_description);
@@ -182,8 +178,6 @@
mWidgetImage.animate().cancel();
mWidgetImage.setDrawable(null);
mWidgetImage.setVisibility(View.VISIBLE);
- mWidgetBadge.setImageDrawable(null);
- mWidgetBadge.setVisibility(View.GONE);
mWidgetName.setText(null);
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
@@ -397,17 +391,6 @@
}
}
- /** Used to show the badge when the widget is in the recommended section
- */
- public void showBadge() {
- if (Process.myUserHandle().equals(mItem.user)) {
- mWidgetBadge.setVisibility(View.GONE);
- } else {
- mWidgetBadge.setVisibility(View.VISIBLE);
- mWidgetBadge.setImageResource(R.drawable.ic_work_app_badge);
- }
- }
-
/**
* Shows or hides the long description displayed below each widget.
*
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index f0a23be..f332054 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -24,8 +24,6 @@
import android.util.AttributeSet;
import android.view.View;
-import com.android.launcher3.R;
-
/**
* View that draws a bitmap horizontally centered. If the image width is greater than the view
* width, the image is scaled down appropriately.
@@ -33,8 +31,6 @@
public class WidgetImageView extends View {
private final RectF mDstRectF = new RectF();
- private final int mBadgeMargin;
-
private Drawable mDrawable;
public WidgetImageView(Context context) {
@@ -47,9 +43,6 @@
public WidgetImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
-
- mBadgeMargin = context.getResources()
- .getDimensionPixelSize(R.dimen.profile_badge_margin);
}
/** Set the drawable to use for this view. */
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index e6b9c9b..f1b80e4 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -280,6 +280,6 @@
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
- target.setInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom), interpolator);
+ target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 5cf8203..85375ee 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_DIALOG_SEEN;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
@@ -814,8 +813,7 @@
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
- target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
- target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
+ target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator);
}
@Override
@@ -911,7 +909,7 @@
public void onBackInvoked() {
if (mIsInSearchMode) {
mSearchBar.reset();
- animateSlideInViewToNoScale();
+ animateSwipeToDismissProgressToStart();
} else {
super.onBackInvoked();
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index c60bca0..1bf813c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -74,7 +74,7 @@
private ScrollView mRightPaneScrollView;
private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
- private boolean mOldIsBackSwipeProgressing;
+ private boolean mOldIsSwipeToDismissInProgress;
private int mActivePage = -1;
private PackageUserKey mSelectedHeader;
@@ -154,14 +154,14 @@
}
@Override
- protected void onScaleProgressChanged() {
- super.onScaleProgressChanged();
- boolean isBackSwipeProgressing = mSlideInViewScale.value > 0;
- if (isBackSwipeProgressing == mOldIsBackSwipeProgressing) {
+ protected void onUserSwipeToDismissProgressChanged() {
+ super.onUserSwipeToDismissProgressChanged();
+ boolean isSwipeToDismissInProgress = mSwipeToDismissProgress.value > 0;
+ if (isSwipeToDismissInProgress == mOldIsSwipeToDismissInProgress) {
return;
}
- mOldIsBackSwipeProgressing = isBackSwipeProgressing;
- if (isBackSwipeProgressing) {
+ mOldIsSwipeToDismissInProgress = isSwipeToDismissInProgress;
+ if (isSwipeToDismissInProgress) {
modifyAttributesOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent,
CLIP_CHILDREN_FALSE_MODIFIER);
modifyAttributesOnViewTree(mRightPaneScrollView, (ViewParent) mContent,
diff --git a/tests/src/com/android/launcher3/LauncherIntentTest.java b/tests/src/com/android/launcher3/LauncherIntentTest.java
index e8822c3..a3013c7 100644
--- a/tests/src/com/android/launcher3/LauncherIntentTest.java
+++ b/tests/src/com/android/launcher3/LauncherIntentTest.java
@@ -29,7 +29,6 @@
import com.android.launcher3.allapps.SearchRecyclerView;
import com.android.launcher3.ui.AbstractLauncherUiTest;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,7 +39,6 @@
public final Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS);
@Test
- @Ignore("b/329152799")
public void testAllAppsIntent() {
// setup by moving to home
mLauncher.goHome();
@@ -66,8 +64,6 @@
// Highlights the search bar, then fills text to display the SearchView.
private void moveToSearchView() {
- mLauncher.goHome().switchToAllApps();
-
// All Apps view should be loaded
assertTrue("Launcher internal state is not All Apps",
isInState(() -> LauncherState.ALL_APPS));
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 476bda5..e9d2f6e 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
@@ -39,6 +38,7 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -97,6 +97,8 @@
private LauncherApps mLauncherApps;
@Mock
private AllAppsRecyclerView mAllAppsRecyclerView;
+ @Mock
+ private Resources mResources;
@Before
public void setUp() {
@@ -106,6 +108,7 @@
when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO);
when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
when(mAllApps.getContext()).thenReturn(mContext);
+ when(mAllApps.getContext().getResources()).thenReturn(mResources);
when(mAllApps.getAppsStore()).thenReturn(mAllAppsStore);
when(mAllApps.getActiveRecyclerView()).thenReturn(mAllAppsRecyclerView);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
@@ -145,6 +148,7 @@
public void quietModeFlagPresent_privateSpaceIsResetToDisabled() {
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
doNothing().when(privateProfileManager).resetPrivateSpaceDecorator(anyInt());
+ doNothing().when(privateProfileManager).executeLock();
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED))
.thenReturn(false, true);
@@ -181,6 +185,7 @@
public void transitioningToLocked_resetCallsExecuteLock() throws Exception {
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
doNothing().when(privateProfileManager).resetPrivateSpaceDecorator(anyInt());
+ doNothing().when(privateProfileManager).executeLock();
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED))
.thenReturn(true);
doNothing().when(privateProfileManager).expandPrivateSpace();
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
index 7ac276a..351b921 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -303,7 +303,7 @@
assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
mAlphabeticalAppsList.getAdapterItems().size());
assertEquals(position,
- privateProfileManager.scrollForViewToBeVisibleInContainer(
+ privateProfileManager.scrollForHeaderToBeVisibleInContainer(
new AllAppsRecyclerView(mContext),
mAlphabeticalAppsList.getAdapterItems(),
PS_HEADER_HEIGHT,
@@ -339,7 +339,7 @@
assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
mAlphabeticalAppsList.getAdapterItems().size());
assertEquals(position,
- privateProfileManager.scrollForViewToBeVisibleInContainer(
+ privateProfileManager.scrollForHeaderToBeVisibleInContainer(
new AllAppsRecyclerView(mContext),
mAlphabeticalAppsList.getAdapterItems(),
BIGGER_PS_HEADER_HEIGHT,
@@ -369,7 +369,7 @@
// The number of adapterItems should be the private space apps + one main app.
assertEquals(NUM_PRIVATE_SPACE_APPS + 1,
mAlphabeticalAppsList.getAdapterItems().size());
- assertEquals(SCROLL_NO_WHERE, privateProfileManager.scrollForViewToBeVisibleInContainer(
+ assertEquals(SCROLL_NO_WHERE, privateProfileManager.scrollForHeaderToBeVisibleInContainer(
new AllAppsRecyclerView(mContext),
mAlphabeticalAppsList.getAdapterItems(),
BIGGER_PS_HEADER_HEIGHT,
diff --git a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 3a1883c..da14425 100644
--- a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -29,7 +29,7 @@
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.UserBadgeDrawable
import com.android.launcher3.model.data.FolderInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.FlagOp
import com.android.launcher3.util.LauncherLayoutBuilder
@@ -47,7 +47,7 @@
private lateinit var previewItemManager: PreviewItemManager
private lateinit var context: Context
- private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
+ private lateinit var folderItems: ArrayList<ItemInfo>
private lateinit var modelHelper: LauncherModelHelper
private lateinit var folderIcon: FolderIcon
@@ -72,15 +72,17 @@
.build()
)
.loadModelSync()
- folderItems = modelHelper.bgDataModel.collections.valueAt(0).contents
+ folderItems = modelHelper.bgDataModel.collections.valueAt(0).getContents()
folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
- folderIcon.mInfo.contents = folderItems
+ folderIcon.mInfo.getContents().addAll(folderItems)
+ // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
+ val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
// Set first icon to be themed.
- folderItems[0]
+ folderApps[0]
.bitmap
.setMonoIcon(
- folderItems[0].bitmap.icon,
+ folderApps[0].bitmap.icon,
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
@@ -89,7 +91,7 @@
)
// Set second icon to be non-themed.
- folderItems[1]
+ folderApps[1]
.bitmap
.setMonoIcon(
null,
@@ -101,23 +103,21 @@
)
// Set third icon to be themed with badge.
- folderItems[2]
+ folderApps[2]
.bitmap
.setMonoIcon(
- folderItems[2].bitmap.icon,
+ folderApps[2].bitmap.icon,
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
previewItemManager.mIconSize
)
)
- folderItems[2].bitmap =
- folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+ folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
// Set fourth icon to be non-themed with badge.
- folderItems[3].bitmap =
- folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
- folderItems[3]
+ folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+ folderApps[3]
.bitmap
.setMonoIcon(
null,
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index abb0c39..d3a6355 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -160,6 +160,6 @@
}
private List<WorkspaceItemInfo> allItems() {
- return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).getContents();
+ return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).getAppContents();
}
}
diff --git a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index ed587a1..2e209a4 100644
--- a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -168,8 +168,8 @@
val collections = modelHelper.getBgDataModel().collections
assertThat(collections.size()).isEqualTo(1)
- assertThat(collections.valueAt(0).contents.size).isEqualTo(itemCount)
- return collections.valueAt(0).contents
+ assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
+ return collections.valueAt(0).getAppContents()
}
private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 4eae7e1..6780ed5 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -21,6 +21,8 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,6 +30,7 @@
@RunWith(AndroidJUnit4.class)
public class TaplTestsLauncher3Test extends AbstractLauncherUiTest {
+ @ScreenRecord // b/322823478
@Test
public void testDevicePressMenu() throws Exception {
mDevice.pressMenu();
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index a9947a0..eb1f49f 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -195,6 +195,7 @@
}
+ @ScreenRecord // b/322823478
@Test
public void testEdu() {
assumeTrue(mWorkProfileSetupSuccessful);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index 43fc8ff..913dfa2 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -37,6 +37,7 @@
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import org.junit.After;
import org.junit.Before;
@@ -240,6 +241,7 @@
});
}
+ @ScreenRecordRule.ScreenRecord // b/329935119
@Test
@PortraitLandscape
public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {
diff --git a/tests/src/com/android/launcher3/util/ItemInflaterTest.kt b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
index 0065527..dae09bb 100644
--- a/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
+++ b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
@@ -139,9 +139,9 @@
@Test
fun test_folder_inflated_on_UI() {
val itemInfo = FolderInfo()
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
@@ -155,9 +155,9 @@
setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
val itemInfo = FolderInfo()
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
VIEW_PREINFLATION_EXECUTOR.submit(
@@ -173,8 +173,8 @@
fun test_app_pair_inflated_on_UI() {
val itemInfo = AppPairInfo()
itemInfo.itemType = ITEM_TYPE_APP_PAIR
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
@@ -189,8 +189,8 @@
val itemInfo = AppPairInfo()
itemInfo.itemType = ITEM_TYPE_APP_PAIR
- itemInfo.contents.add(workspaceItemInfo())
- itemInfo.contents.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
+ itemInfo.add(workspaceItemInfo())
val view =
VIEW_PREINFLATION_EXECUTOR.submit(
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 3f70bb9..68829e0 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -398,8 +398,9 @@
if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
return false;
}
- if (!mLauncher.isAppPairsEnabled() && task.isTaskSplit()) {
- // Overview actions aren't visible for split screen tasks.
+ if (task.isTaskSplit() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
+ // Overview actions aren't visible for split screen tasks, except for save app pair
+ // button on tablets.
return false;
}
return true;