Merge "Updating test build rules" into sc-v2-dev
diff --git a/buglist_with_title.txt b/buglist_with_title.txt
deleted file mode 100644
index aa8b413..0000000
--- a/buglist_with_title.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-144170434 twickham P1 FIXED Improve Overview -> Home transition ----
-149934536 twickham P2 FIXED Update gesture nav pullback logic ----
-154951045 peanutbutter P1 FIXED Odd animation occuring at times when swiping to home ----
-154964045 awickham P2 FIXED "Clear all" text is not in the middle of app's window vertically ----
-158701272 twickham P4 FIXED Discontinuities when long-swiping to home ----
-160361464 tracyzhou P2 FIXED Place launcher above the target app in live tile mode ----
-160568387 twickham P2 FIXED Can't get to app switcher by swiping up (motion pause not detected) ----
-160718310 xuqiu P1 FIXED With "Select" overview action selected, App icon is missing in other overview apps after orientation change ----
-160748731 sunnygoyal P2 ASSIGNED Unify prediction model with Launcher model ----
-160759508 twickham P2 FIXED Swipe up cannot back to home screen in overview. ----
-161273376 xuqiu P2 FIXED [Overview Actions] Add logging and helpful messages ----
-161536946 twickham P2 FIXED Haptics don't indicate snap-to in overview, ----
-161685099 winsonc P2 FIXED Screen still stay at the quick settings/notification when I swipe up with 3 finger to check the all apps. ----
-161801331 hyunyoungs P2 FIXED Change AllAppsSearch plugin to support only data fetch ----
-161901771 xuqiu P1 FIXED Overlapping layer of highlights with app layout getting darker when keep rotating the device from "Feedback" viewpoint in split screen ----
-161939759 sunnygoyal P2 FIXED RD1A: Going to overview in landscape mode clips the screen content ----
-162012217 perumaal P2 ASSIGNED Leaked Activity Caused by Gleams ----
-162454040 bookatz P2 ASSIGNED Create multiuser test that checks that opening an app works properly ----
-162480567 sfufa P4 FIXED Enable Item Decorations for search items ----
-162564471 tracyzhou P2 FIXED [Live tile] Handle tapping overview actions in live tile mode ----
-162623012 zakcohen P1 ASSIGNED Enable chips flag ----
-162812884 winsonc P2 ASSIGNED [R]The color have not changed in some page after turning on the dark theme. ----
-162861289 hyunyoungs P2 FIXED Add FocusIndicator support to DEVICE_SEARCH feature in S ----
-162871508 sfufa P2 ASSIGNED Introduce support for Hero app section ----
diff --git a/quickstep/res/drawable/button_taskbar_edu_bordered.xml b/quickstep/res/drawable/button_taskbar_edu_bordered.xml
new file mode 100644
index 0000000..47f8e8f
--- /dev/null
+++ b/quickstep/res/drawable/button_taskbar_edu_bordered.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp" />
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/quickstep/res/drawable/button_taskbar_edu_colored.xml b/quickstep/res/drawable/button_taskbar_edu_colored.xml
new file mode 100644
index 0000000..70bfc9f
--- /dev/null
+++ b/quickstep/res/drawable/button_taskbar_edu_colored.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/quickstep/res/drawable/taskbar_edu_splitscreen.webp b/quickstep/res/drawable/taskbar_edu_splitscreen.webp
new file mode 100644
index 0000000..2f4402f
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_edu_splitscreen.webp
Binary files differ
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
new file mode 100644
index 0000000..069ff86
--- /dev/null
+++ b/quickstep/res/layout/task_grouped.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- NOTE! don't add dimensions for margins / paddings / sizes that change per orientation to this
+ file, they need to be loaded at runtime. -->
+
+<!-- DOUBLE NOTE! Don't deviate IDs from task.xml since this layout acts as a "subclass" (read as
+ "bad code"). How can we use the view pool in RecentsView to use task.xml layout with using
+ GroupedTaskView.java class? Is that possible (while still keeping code in separate class) ? -->
+
+<com.android.quickstep.views.GroupedTaskView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:defaultFocusHighlightEnabled="false"
+ android:focusable="true">
+
+ <com.android.quickstep.views.TaskThumbnailView
+ android:id="@+id/snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <com.android.quickstep.views.TaskThumbnailView
+ android:id="@+id/bottomright_snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <com.android.quickstep.views.IconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/task_thumbnail_icon_size"
+ android:layout_height="@dimen/task_thumbnail_icon_size"
+ android:focusable="false"
+ android:importantForAccessibility="no"/>
+</com.android.quickstep.views.GroupedTaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index c0e0862..b4c168c 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -65,13 +65,12 @@
android:layout_gravity="end"/>
</FrameLayout>
- <View
+ <com.android.launcher3.taskbar.StashedHandleView
android:id="@+id/stashed_handle"
tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- tools:comment2="TODO: Tint dynamically"
- android:background="?android:attr/textColorPrimary"
+ android:background="@color/taskbar_stashed_handle_dark_color"
android:clipToOutline="true"
android:layout_gravity="bottom"/>
diff --git a/quickstep/res/layout/taskbar_edu.xml b/quickstep/res/layout/taskbar_edu.xml
new file mode 100644
index 0000000..b7717b7
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.taskbar.TaskbarEduView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:gravity="bottom"
+ android:orientation="vertical"
+ android:layout_marginHorizontal="108dp">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/edu_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/bg_rounded_corner_bottom_sheet"
+ android:gravity="center_horizontal"
+ android:paddingHorizontal="36dp"
+ android:paddingTop="64dp">
+
+ <TextView
+ android:id="@+id/edu_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp"
+ app:layout_constraintTop_toTopOf="parent"
+ android:gravity="center_horizontal"
+ style="@style/TextHeadline"
+ android:text="@string/taskbar_edu_header_1"
+ android:fontFamily="google-sans"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"
+ android:maxLines="2"/>
+
+ <ImageView
+ android:id="@+id/edu_illustration"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ app:layout_constraintTop_toBottomOf="@id/edu_header"
+ android:src="@drawable/taskbar_edu_splitscreen"/>
+
+ <Button
+ android:id="@+id/edu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ android:layout_marginBottom="92dp"
+ app:layout_constraintTop_toBottomOf="@id/edu_illustration"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:text="@string/taskbar_edu_close"
+ style="@style/TaskbarEdu.Button.Close"
+ android:textColor="?android:attr/textColorPrimary"/>
+
+ <Button
+ android:id="@+id/edu_next_button"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ android:layout_marginBottom="92dp"
+ app:layout_constraintTop_toBottomOf="@id/edu_illustration"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:text="@string/taskbar_edu_next"
+ style="@style/TaskbarEdu.Button.Next"
+ android:textColor="?androidprv:attr/textColorOnAccent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.launcher3.taskbar.TaskbarEduView>
\ No newline at end of file
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 73e4e0c..ef8f3ba 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -33,13 +33,13 @@
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"మీ సూచించబడిన యాప్లు"</string>
<string name="hotseat_edu_title_migrate" msgid="306578144424489980">"మీ మొదటి స్క్రీన్ దిగువ వరుసలో యాప్ సలహాలను పొందండి"</string>
<string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"మీ హోమ్ స్క్రీన్లోని ఇష్టమైన వాటి వరుసలో యాప్ సూచనలు పొందండి"</string>
- <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు మీ మొదటి స్క్రీన్ పైకి చేరుకుంటాయి."</string>
- <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్లు మీ మొదటి స్క్రీన్కు చేరుకుంటాయి."</string>
- <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు కొత్త ఫోల్డర్కు తరలించబడతాయి."</string>
+ <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు మీ మొదటి స్క్రీన్ పైకి చేరుకుంటాయి."</string>
+ <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్లు మీ మొదటి స్క్రీన్కు చేరుకుంటాయి."</string>
+ <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు కొత్త ఫోల్డర్కు తరలించబడతాయి."</string>
<string name="hotseat_edu_accept" msgid="1611544083278999837">"యాప్ సూచనలను పొందండి"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"వద్దు"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"సెట్టింగ్లు"</string>
- <string name="hotseat_auto_enrolled" msgid="522100018967146807">"ఎక్కువగా ఉపయోగించిన యాప్లు ఇక్కడ కనిపిస్తాయి, అవి రోజువారీ కార్యకలాపాలను బట్టి మారుతూ ఉంటాయి"</string>
+ <string name="hotseat_auto_enrolled" msgid="522100018967146807">"ఎక్కువగా ఉపయోగించిన యాప్లు ఇక్కడ కనిపిస్తాయి, అవి రోజువారీ యాక్టివిటీలను బట్టి మారుతూ ఉంటాయి"</string>
<string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"యాప్ సలహాలను పొందడానికి దిగువ వరుస నుండి యాప్లను లాగండి"</string>
<string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"యాప్ సూచనలు ఖాళీ స్పేస్కు జోడించబడ్డాయి"</string>
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి"</string>
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 2f24441..17980f0 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -28,4 +28,7 @@
<!-- Taskbar -->
<color name="taskbar_background">@color/overview_scrim_dark</color>
<color name="taskbar_icon_selection_ripple">#E0E0E0</color>
+
+ <color name="taskbar_stashed_handle_light_color">#EBffffff</color>
+ <color name="taskbar_stashed_handle_dark_color">#99000000</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 659ff9a..f1f23c4 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -32,7 +32,8 @@
<dimen name="overview_minimum_next_prev_size">50dp</dimen>
<dimen name="overview_task_margin">16dp</dimen>
- <dimen name="overview_task_margin_grid">12dp</dimen>
+ <dimen name="overview_task_margin_focused">12dp</dimen>
+ <dimen name="overview_task_margin_grid">4dp</dimen>
<!-- Overrideable in overlay that provides the Overview Actions. -->
<dimen name="overview_actions_height">48dp</dimen>
@@ -45,8 +46,8 @@
<dimen name="overview_actions_horizontal_margin">16dp</dimen>
<dimen name="overview_grid_side_margin">50dp</dimen>
- <dimen name="overview_grid_row_spacing_portrait">37.13dp</dimen>
- <dimen name="overview_grid_row_spacing_landscape">33.38dp</dimen>
+ <dimen name="overview_grid_row_spacing_portrait">17.13dp</dimen>
+ <dimen name="overview_grid_row_spacing_landscape">13.38dp</dimen>
<dimen name="overview_grid_focus_vertical_margin">0dp</dimen>
<!-- These speeds are in dp/s -->
@@ -165,4 +166,5 @@
<dimen name="taskbar_stashed_size">24dp</dimen>
<dimen name="taskbar_stashed_handle_width">220dp</dimen>
<dimen name="taskbar_stashed_handle_height">6dp</dimen>
+ <dimen name="taskbar_edu_bg_corner_radius">28dp</dimen>
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 4aee2a9..da19c95 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -203,4 +203,13 @@
<string name="gesture_tutorial_action_button_label_cancel">Cancel</string>
<!-- Button text shown on a button on the tutorial skip dialog to exit the tutorial. [CHAR LIMIT=14] -->
<string name="gesture_tutorial_action_button_label_skip">Skip</string>
+
+ <!-- ******* Taskbar Edu ******* -->
+ <!-- Text in dialog that lets a user know how they can use the taskbar on their device.
+ [CHAR_LIMIT=NONE] -->
+ <string name="taskbar_edu_header_1">Use 2 apps at once and switch apps with the taskbar</string>
+ <!-- Text on button to go to the next screen of a tutorial [CHAR_LIMIT=30] -->
+ <string name="taskbar_edu_next">Next</string>
+ <!-- Text on button to exit a tutorial [CHAR_LIMIT=30] -->
+ <string name="taskbar_edu_close">Close</string>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 07c448d..c23a3a0 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -139,4 +139,16 @@
<style name="BaseIcon.Workspace.Taskbar" >
<item name="iconDisplay">taskbar</item>
</style>
+
+ <style name="TaskbarEdu.Button.Close" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/button_taskbar_edu_bordered</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="TaskbarEdu.Button.Next" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/button_taskbar_edu_colored</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:textSize">16sp</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index 9df9ab1..82e8903 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -63,7 +63,7 @@
RunningTaskInfo placeholderTask = new RunningTaskInfo();
placeholderTask.taskId = 22;
- frv.showCurrentTask(placeholderTask);
+ frv.showCurrentTask(new RunningTaskInfo[]{placeholderTask});
doLayout(activity);
ThumbnailData thumbnailData = new ThumbnailData();
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 2da8a45..433ae3c 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -85,6 +85,7 @@
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.RunnableList;
@@ -427,6 +428,10 @@
4 - rotationChange);
}
}
+ // TODO(b/196637509): don't do this for immersive apps.
+ if (mDeviceProfile.isTaskbarPresentInApps) {
+ bounds.bottom -= mDeviceProfile.taskbarSize;
+ }
return bounds;
}
@@ -511,7 +516,10 @@
final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get();
if (scrimEnabled) {
- int scrimColor = Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor);
+ boolean useTaskbarColor = mDeviceProfile.isTaskbarPresentInApps;
+ int scrimColor = useTaskbarColor
+ ? mLauncher.getResources().getColor(R.color.taskbar_background)
+ : Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor);
int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0);
int[] colors = isAppOpening
? new int[]{scrimColorTrans, scrimColor}
@@ -524,6 +532,30 @@
colors);
scrim.setDuration(CONTENT_SCRIM_DURATION);
scrim.setInterpolator(DEACCEL_1_5);
+
+ if (useTaskbarColor) {
+ // Hide the taskbar background color since it would duplicate the scrim.
+ scrim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ mLauncher.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ taskbarUIController.forceHideBackground(true);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ mLauncher.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ taskbarUIController.forceHideBackground(false);
+ }
+ }
+ });
+ }
+
launcherAnimator.play(scrim);
}
}
@@ -638,6 +670,10 @@
if (v instanceof BubbleTextView) {
((BubbleTextView) v).setStayPressed(false);
}
+ LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+ if (taskbarController != null) {
+ taskbarController.showEdu();
+ }
openingTargets.release();
}
});
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 56c28f0..da10bfb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -23,16 +23,20 @@
import android.animation.ObjectAnimator;
import android.graphics.Rect;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
@@ -58,9 +62,13 @@
private final AnimatedFloat mIconAlignmentForGestureState =
new AnimatedFloat(this::onIconAlignmentRatioChanged);
+ private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
+ this::onStashedInAppChanged;
+
// Initialized in init.
private TaskbarControllers mControllers;
private AnimatedFloat mTaskbarBackgroundAlpha;
+ private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
private AlphaProperty mIconAlphaForHome;
private boolean mIsAnimatingToLauncherViaResume;
private boolean mIsAnimatingToLauncherViaGesture;
@@ -84,6 +92,8 @@
mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
.getTaskbarBackgroundAlpha();
+ mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
+ .getOverrideBackgroundAlpha();
MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
@@ -94,6 +104,9 @@
onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
mIconAlignmentForResumedState.finishAnimation();
onIconAlignmentRatioChanged();
+
+ onStashedInAppChanged(mLauncher.getDeviceProfile());
+ mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
}
@Override
@@ -102,6 +115,7 @@
mIconAlignmentForResumedState.finishAnimation();
mIconAlignmentForGestureState.finishAnimation();
+ mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
mLauncher.getHotseat().setIconsAlpha(1f);
mLauncher.setTaskbarUIController(null);
}
@@ -192,7 +206,7 @@
}
private float getCurrentIconAlignmentRatio() {
- return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+ return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
}
private void onIconAlignmentRatioChanged() {
@@ -228,11 +242,56 @@
return mContext.getDragController().isDragging();
}
- void setTaskbarViewVisible(boolean isVisible) {
+ public View getRootView() {
+ return mTaskbarDragLayer;
+ }
+
+ private void setTaskbarViewVisible(boolean isVisible) {
mIconAlphaForHome.setValue(isVisible ? 1 : 0);
mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
}
+ @Override
+ protected void onStashedInAppChanged() {
+ onStashedInAppChanged(mLauncher.getDeviceProfile());
+ }
+
+ private void onStashedInAppChanged(DeviceProfile deviceProfile) {
+ boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
+ deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
+ }
+
+ /**
+ * Sets whether the background behind the taskbar/nav bar should be hidden.
+ */
+ public void forceHideBackground(boolean forceHide) {
+ mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
+ }
+
+ /**
+ * Starts the taskbar education flow, if the user hasn't seen it yet.
+ */
+ public void showEdu() {
+ if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()
+ || mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN)) {
+ return;
+ }
+ mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
+
+ mControllers.taskbarEduController.showEdu();
+ }
+
+ /**
+ * Manually ends the taskbar education flow.
+ */
+ public void hideEdu() {
+ if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()) {
+ return;
+ }
+
+ mControllers.taskbarEduController.hideEdu();
+ }
+
private final class TaskBarRecentsAnimationListener implements RecentsAnimationListener {
private final RecentsAnimationCallbacks mCallbacks;
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
new file mode 100644
index 0000000..0224bc4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.R;
+
+public class StashedHandleView extends View {
+
+ private final @ColorInt int mStashedHandleLightColor;
+ private final @ColorInt int mStashedHandleDarkColor;
+ private final Rect mSampledRegion = new Rect();
+ private final int[] mTmpArr = new int[2];
+
+ public StashedHandleView(Context context) {
+ this(context, null);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mStashedHandleLightColor = ContextCompat.getColor(context,
+ R.color.taskbar_stashed_handle_light_color);
+ mStashedHandleDarkColor = ContextCompat.getColor(context,
+ R.color.taskbar_stashed_handle_dark_color);
+ }
+
+ public void updateSampledRegion() {
+ getLocationOnScreen(mTmpArr);
+ mSampledRegion.set(mTmpArr[0], mTmpArr[1], mTmpArr[0] + getWidth(),
+ mTmpArr[1] + getHeight());
+ }
+
+ public Rect getSampledRegion() {
+ return mSampledRegion;
+ }
+
+ public void updateHandleColor(boolean isRegionDark) {
+ setBackgroundColor(isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index df37261..2858d7c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import android.animation.Animator;
+import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -25,19 +26,30 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.util.Executors;
import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
/**
* Handles properties/data collection, then passes the results to our stashed handle View to render.
*/
public class StashedHandleViewController {
+ /**
+ * The SharedPreferences key for whether the stashed handle region is dark.
+ */
+ private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY =
+ "stashed_handle_region_is_dark";
+
private final TaskbarActivityContext mActivity;
- private final View mStashedHandleView;
+ private final SharedPreferences mPrefs;
+ private final StashedHandleView mStashedHandleView;
private final int mStashedHandleWidth;
private final int mStashedHandleHeight;
+ private final RegionSamplingHelper mRegionSamplingHelper;
private final AnimatedFloat mTaskbarStashedHandleAlpha = new AnimatedFloat(
this::updateStashedHandleAlpha);
private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
@@ -52,13 +64,31 @@
private boolean mIsAtStashedRevealBounds = true;
- public StashedHandleViewController(TaskbarActivityContext activity, View stashedHandleView) {
+ public StashedHandleViewController(TaskbarActivityContext activity,
+ StashedHandleView stashedHandleView) {
mActivity = activity;
+ mPrefs = Utilities.getPrefs(mActivity);
mStashedHandleView = stashedHandleView;
+ mStashedHandleView.updateHandleColor(
+ mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false));
final Resources resources = mActivity.getResources();
mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
mStashedHandleHeight = resources.getDimensionPixelSize(
R.dimen.taskbar_stashed_handle_height);
+ mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
+ new RegionSamplingHelper.SamplingCallback() {
+ @Override
+ public void onRegionDarknessChanged(boolean isRegionDark) {
+ mStashedHandleView.updateHandleColor(isRegionDark);
+ mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
+ isRegionDark).apply();
+ }
+
+ @Override
+ public Rect getSampledRegion(View sampledView) {
+ return mStashedHandleView.getSampledRegion();
+ }
+ }, Executors.UI_HELPER_EXECUTOR);
}
public void init(TaskbarControllers controllers) {
@@ -93,6 +123,10 @@
});
}
+ public void onDestroy() {
+ mRegionSamplingHelper.stopAndDestroy();
+ }
+
public AnimatedFloat getStashedHandleAlpha() {
return mTaskbarStashedHandleAlpha;
}
@@ -117,6 +151,16 @@
return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
}
+ public void onIsStashed(boolean isStashed) {
+ mRegionSamplingHelper.setWindowVisible(isStashed);
+ if (isStashed) {
+ mStashedHandleView.updateSampledRegion();
+ mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
+ } else {
+ mRegionSamplingHelper.stop();
+ }
+ }
+
protected void updateStashedHandleAlpha() {
mStashedHandleView.setAlpha(mTaskbarStashedHandleAlpha.value);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index dbe528f..5b9bd31 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -36,6 +36,7 @@
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.RoundedCorner;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -83,6 +84,7 @@
private final TaskbarControllers mControllers;
private final WindowManager mWindowManager;
+ private final RoundedCorner mLeftCorner, mRightCorner;
private WindowManager.LayoutParams mWindowLayoutParams;
private boolean mIsFullscreen;
// The size we should return to when we call setTaskbarWindowFullscreen(false)
@@ -115,7 +117,7 @@
R.layout.taskbar, null, false);
TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
- View stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
+ StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
// Construct controllers.
mControllers = new TaskbarControllers(this,
@@ -128,17 +130,20 @@
new TaskbarViewController(this, taskbarView),
new TaskbarKeyguardController(this),
new StashedHandleViewController(this, stashedHandleView),
- new TaskbarStashController(this));
+ new TaskbarStashController(this),
+ new TaskbarEduController(this));
Display display = windowContext.getDisplay();
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
? windowContext.getApplicationContext()
: windowContext.getApplicationContext().createDisplayContext(display);
mWindowManager = c.getSystemService(WindowManager.class);
+ mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+ mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
}
public void init() {
- mLastRequestedNonFullscreenHeight = mDeviceProfile.taskbarSize;
+ mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
mWindowLayoutParams = new WindowManager.LayoutParams(
MATCH_PARENT,
mLastRequestedNonFullscreenHeight,
@@ -168,6 +173,14 @@
return mNavMode == Mode.THREE_BUTTONS;
}
+ public RoundedCorner getLeftCorner() {
+ return mLeftCorner;
+ }
+
+ public RoundedCorner getRightCorner() {
+ return mRightCorner;
+ }
+
@Override
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
@@ -249,8 +262,12 @@
setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenHeight);
}
+ public boolean isTaskbarWindowFullscreen() {
+ return mIsFullscreen;
+ }
+
/**
- * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
+ * Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset).
*/
public void setTaskbarWindowHeight(int height) {
if (mWindowLayoutParams.height == height || mIsDestroyed) {
@@ -270,6 +287,14 @@
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
+ /**
+ * Returns the default height of the window, including the static corner radii above taskbar.
+ */
+ public int getDefaultTaskbarWindowHeight() {
+ return mDeviceProfile.taskbarSize
+ + Math.max(mLeftCorner.getRadius(), mRightCorner.getRadius());
+ }
+
protected void onTaskbarIconClicked(View view) {
Object tag = view.getTag();
if (tag instanceof Task) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index be26913..b32a41e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -34,6 +34,7 @@
public final TaskbarKeyguardController taskbarKeyguardController;
public final StashedHandleViewController stashedHandleViewController;
public final TaskbarStashController taskbarStashController;
+ public final TaskbarEduController taskbarEduController;
/** Do not store this controller, as it may change at runtime. */
@NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
@@ -47,7 +48,8 @@
TaskbarViewController taskbarViewController,
TaskbarKeyguardController taskbarKeyguardController,
StashedHandleViewController stashedHandleViewController,
- TaskbarStashController taskbarStashController) {
+ TaskbarStashController taskbarStashController,
+ TaskbarEduController taskbarEduController) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
this.navButtonController = navButtonController;
@@ -58,6 +60,7 @@
this.taskbarKeyguardController = taskbarKeyguardController;
this.stashedHandleViewController = stashedHandleViewController;
this.taskbarStashController = taskbarStashController;
+ this.taskbarEduController = taskbarEduController;
}
/**
@@ -86,5 +89,6 @@
taskbarDragLayerController.onDestroy();
taskbarKeyguardController.onDestroy();
taskbarViewController.onDestroy();
+ stashedHandleViewController.onDestroy();
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index cd1baf7..0848c35 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -40,9 +41,12 @@
public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
private final Paint mTaskbarBackgroundPaint;
+ private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+ // Initialized in init.
private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
+ private float mLeftCornerRadius, mRightCornerRadius;
private float mTaskbarBackgroundOffset;
@@ -65,10 +69,32 @@
mTaskbarBackgroundPaint = new Paint();
mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
mTaskbarBackgroundPaint.setAlpha(0);
+ mTaskbarBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mTaskbarBackgroundPaint.setStyle(Paint.Style.FILL);
+
+ // Will be set in init(), but this ensures they are always non-null.
+ mInvertedLeftCornerPath = new Path();
+ mInvertedRightCornerPath = new Path();
}
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
mControllerCallbacks = callbacks;
+
+ // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
+ // square, and then subtracting out a circle from the appropriate corner.
+ mLeftCornerRadius = mActivity.getLeftCorner().getRadius();
+ mRightCornerRadius = mActivity.getRightCorner().getRadius();
+ Path square = new Path();
+ square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
+ Path circle = new Path();
+ circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
+ mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+ square.reset();
+ square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
+ circle.reset();
+ circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
+ mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+
recreateControllers();
}
@@ -121,8 +147,20 @@
protected void dispatchDraw(Canvas canvas) {
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
* (1f - mTaskbarBackgroundOffset);
- canvas.drawRect(0, canvas.getHeight() - backgroundHeight, canvas.getWidth(),
- canvas.getHeight(), mTaskbarBackgroundPaint);
+ canvas.save();
+ canvas.translate(0, canvas.getHeight() - backgroundHeight);
+
+ // Draw the background behind taskbar content.
+ canvas.drawRect(0, 0, canvas.getWidth(), backgroundHeight, mTaskbarBackgroundPaint);
+
+ // Draw the inverted rounded corners above the taskbar.
+ canvas.translate(0, -mLeftCornerRadius);
+ canvas.drawPath(mInvertedLeftCornerPath, mTaskbarBackgroundPaint);
+ canvas.translate(0, mLeftCornerRadius);
+ canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
+ canvas.drawPath(mInvertedRightCornerPath, mTaskbarBackgroundPaint);
+
+ canvas.restore();
super.dispatchDraw(canvas);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index df89285..0afa480 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -36,10 +37,14 @@
private final TaskbarActivityContext mActivity;
private final TaskbarDragLayer mTaskbarDragLayer;
private final int mFolderMargin;
+
// Alpha properties for taskbar background.
private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha);
private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
+ private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
+
// Translation property for taskbar background.
private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset);
@@ -58,6 +63,7 @@
mControllers = controllers;
mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
mKeyguardBgTaskbar.value = 1;
+ mBgOverride.value = 1;
}
public void onDestroy() {
@@ -86,13 +92,19 @@
return mKeyguardBgTaskbar;
}
+ public AnimatedFloat getOverrideBackgroundAlpha() {
+ return mBgOverride;
+ }
+
public AnimatedFloat getTaskbarBackgroundOffset() {
return mBgOffset;
}
private void updateBackgroundAlpha() {
+ final float bgNavbar = mBgNavbar.value;
+ final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value;
mTaskbarDragLayer.setTaskbarBackgroundAlpha(
- Math.max(mBgNavbar.value, mBgTaskbar.value * mKeyguardBgTaskbar.value)
+ mBgOverride.value * Math.max(bgNavbar, bgTaskbar)
);
}
@@ -121,13 +133,14 @@
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
} else if (mControllers.navbarButtonsViewController.isImeVisible()) {
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_CONTENT);
} else if (!mControllers.uiController.isTaskbarTouchable()) {
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
} else if (mControllers.taskbarViewController.areIconsVisible()) {
// Buttons are visible, take over the full taskbar area
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ insetsInfo.setTouchableInsets(mActivity.isTaskbarWindowFullscreen()
+ ? TOUCHABLE_INSETS_FRAME : TOUCHABLE_INSETS_CONTENT);
} else {
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
new file mode 100644
index 0000000..c5a0fc1
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import com.android.launcher3.R;
+
+/** Handles the Taskbar Education flow. */
+public class TaskbarEduController {
+
+ private final TaskbarActivityContext mActivity;
+ private TaskbarEduView mTaskbarEduView;
+
+ public TaskbarEduController(TaskbarActivityContext activity) {
+ mActivity = activity;
+ }
+
+ void showEdu() {
+ mActivity.setTaskbarWindowFullscreen(true);
+ mActivity.getDragLayer().post(() -> {
+ mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate(
+ R.layout.taskbar_edu, mActivity.getDragLayer(), false);
+ mTaskbarEduView.addOnCloseListener(() -> {
+ mTaskbarEduView = null;
+ });
+ mTaskbarEduView.show();
+ });
+ }
+
+ void hideEdu() {
+ if (mTaskbarEduView != null) {
+ mTaskbarEduView.close(true /* animate */);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
new file mode 100644
index 0000000..b5dab7e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
+import com.android.launcher3.views.AbstractSlideInView;
+
+/** Education view about the Taskbar. */
+public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
+ implements Insettable {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+
+ private final Rect mInsets = new Rect();
+
+ public TaskbarEduView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public TaskbarEduView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(animate, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_TASKBAR_EDUCATION_DIALOG) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mContent = findViewById(R.id.edu_view);
+ findViewById(R.id.edu_close_button).setOnClickListener(v -> close(true /* animate */));
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ mContent.setPadding(mContent.getPaddingStart(),
+ mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
+ }
+
+ @Override
+ protected void attachToContainer() {
+ if (mColorScrim != null) {
+ getPopupContainer().addView(mColorScrim, 0);
+ }
+ getPopupContainer().addView(this, 1);
+ }
+
+ /** Show the Education flow. */
+ public void show() {
+ attachToContainer();
+ animateOpen();
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return context.getResources().getColor(R.color.widgets_picker_scrim);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int width = r - l;
+ int height = b - t;
+
+ // Lay out the content as center bottom aligned.
+ int contentWidth = mContent.getMeasuredWidth();
+ int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
+ mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
+ contentLeft + contentWidth, height);
+
+ setTranslationShift(mTranslationShift);
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 0efec53..1f5ff02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -197,6 +197,7 @@
if (wasStashed != isStashed) {
SystemUiProxy.INSTANCE.get(mActivity)
.notifyTaskbarStatus(/* visible */ true, /* stashed */ isStashed);
+ mControllers.uiController.onStashedInAppChanged();
createAnimToIsStashed(isStashed, TASKBAR_STASH_DURATION).start();
return true;
}
@@ -284,6 +285,7 @@
@Override
public void onAnimationStart(Animator animation) {
mIsStashed = isStashed;
+ onIsStashed(mIsStashed);
}
@Override
@@ -325,4 +327,8 @@
animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
}
+
+ private void onIsStashed(boolean isStashed) {
+ mControllers.stashedHandleViewController.onIsStashed(isStashed);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 260cedc..df88e02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -33,4 +33,6 @@
}
protected void updateContentInsets(Rect outContentInsets) { }
+
+ protected void onStashedInAppChanged() { }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 6b95f08..1882762 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -165,8 +165,9 @@
int offsetY = launcherDp.getTaskbarOffsetY();
setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
- int collapsedHeight = mActivity.getDeviceProfile().taskbarSize;
- int expandedHeight = collapsedHeight + offsetY;
+ int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
+ int expandedHeight = Math.max(collapsedHeight,
+ mActivity.getDeviceProfile().taskbarSize + offsetY);
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index f653906..e12f42e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -33,6 +34,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -53,12 +55,14 @@
PluginInitializerImpl pluginInitializer = new PluginInitializerImpl();
mPluginEnabler = new PluginEnablerImpl(c);
PluginInstanceManager.Factory instanceManagerFactory = new PluginInstanceManager.Factory(
- c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR, pluginInitializer);
+ c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR, pluginInitializer,
+ c.getSystemService(NotificationManager.class), mPluginEnabler,
+ Arrays.asList(pluginInitializer.getPrivilegedPlugins(c)));
mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
pluginInitializer.isDebuggable(),
Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
- new PluginPrefs(c), pluginInitializer.getPrivilegedPlugins(c));
+ new PluginPrefs(c), Arrays.asList(pluginInitializer.getPrivilegedPlugins(c)));
}
public PluginEnablerImpl getPluginEnabler() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index fe5a347..4984b95 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -23,6 +23,7 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -89,6 +90,10 @@
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.isTaskbarPresentInApps) {
+ return launcher.getColor(R.color.taskbar_background);
+ }
return Color.TRANSPARENT;
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 42c89fd..fe16b33 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -99,6 +99,7 @@
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
@@ -106,6 +107,7 @@
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
+import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -222,7 +224,7 @@
protected final TaskAnimationManager mTaskAnimationManager;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
- private RunningWindowAnim mRunningWindowAnim;
+ private RunningWindowAnim[] mRunningWindowAnim;
// Possible second animation running at the same time as mRunningWindowAnim
private Animator mParallelRunningAnim;
private boolean mIsMotionPaused;
@@ -253,6 +255,10 @@
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
protected boolean mIsSwipingPipToHome;
+ // TODO(b/195473090) no split PIP for now, remove once we have more clarity
+ // can try to have RectFSpringAnim evaluate multiple rects at once
+ private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators =
+ new SwipePipToHomeAnimator[2];
// Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
private final float mQuickSwitchScaleScrollThreshold;
@@ -426,7 +432,8 @@
// RecentsView never updates the display rotation until swipe-up, force update
// RecentsOrientedState before passing to TaskViewSimulator.
mRecentsView.updateRecentsRotation();
- mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState());
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+ .setOrientationState(mRecentsView.getPagedViewOrientedState()));
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
@@ -519,7 +526,21 @@
}
protected void notifyGestureAnimationStartToRecents() {
- mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
+ ActivityManager.RunningTaskInfo[] runningTasks;
+ if (mIsSwipeForStagedSplit) {
+ int[] splitTaskIds =
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds();
+ runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
+ for (int i = 0; i < splitTaskIds.length; i++) {
+ int taskId = splitTaskIds[i];
+ ActivityManager.RunningTaskInfo rti = new ActivityManager.RunningTaskInfo();
+ rti.taskId = taskId;
+ runningTasks[i] = rti;
+ }
+ } else {
+ runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
+ }
+ mRecentsView.onGestureAnimationStart(runningTasks);
}
private void launcherFrameDrawn() {
@@ -606,15 +627,15 @@
if (animate) {
ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
reapplyWindowTransformAnim.addUpdateListener(anim -> {
- if (mRunningWindowAnim == null) {
- applyWindowTransform();
+ if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) {
+ applyScrollAndTransform();
}
});
reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
reapplyWindowTransformAnim::cancel);
} else {
- applyWindowTransform();
+ applyScrollAndTransform();
}
}
@@ -655,8 +676,13 @@
private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
mLauncherTransitionController = anim;
- mLauncherTransitionController.getNormalController().dispatchOnStart();
- updateLauncherTransitionProgress();
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
+ // Wait until the gesture is started (touch slop was passed) to start in sync with
+ // mWindowTransitionController. This ensures we don't hide the taskbar background when
+ // long pressing to stash it, for instance.
+ mLauncherTransitionController.getNormalController().dispatchOnStart();
+ updateLauncherTransitionProgress();
+ });
}
public Intent getLaunchIntent() {
@@ -678,7 +704,7 @@
}
updateSysUiFlags(mCurrentShift.value);
- applyWindowTransform();
+ applyScrollAndTransform();
updateLauncherTransitionProgress();
}
@@ -724,24 +750,23 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
- ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+ super.onRecentsAnimationStart(controller, targets);
mRecentsAnimationController = controller;
mRecentsAnimationTargets = targets;
- mTransformParams.setTargetSet(mRecentsAnimationTargets);
- RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
- mGestureState.getRunningTaskId());
-
- if (runningTaskTarget != null) {
- mTaskViewSimulator.setPreview(runningTaskTarget);
- }
// Only initialize the device profile, if it has not been initialized before, as in some
// configurations targets.homeContentInsets may not be correct.
if (mActivity == null) {
- DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
- if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+ RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[0];
+ // orientation state is independent of which remote target handle we use since both
+ // should be pointing to the same one. Just choose index 0 for now since that works for
+ // both split and non-split
+ RecentsOrientedState orientationState = mRemoteTargetHandles[0].mTaskViewSimulator
+ .getOrientationState();
+ DeviceProfile dp = orientationState.getLauncherDeviceProfile();
+ if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
Rect overviewStackBounds = mActivityInterface
- .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
dp = dp.getMultiWindowProfile(mContext,
new WindowBounds(overviewStackBounds, targets.homeContentInsets));
} else {
@@ -751,7 +776,7 @@
dp.updateInsets(targets.homeContentInsets);
dp.updateIsSeascape(mContext);
initTransitionEndpoints(dp);
- mTaskViewSimulator.getOrientationState().setMultiWindowMode(dp.isMultiWindowMode);
+ orientationState.setMultiWindowMode(dp.isMultiWindowMode);
}
// Notify when the animation starts
@@ -869,9 +894,17 @@
private void endRunningWindowAnim(boolean cancel) {
if (mRunningWindowAnim != null) {
if (cancel) {
- mRunningWindowAnim.cancel();
+ for (RunningWindowAnim r : mRunningWindowAnim) {
+ if (r != null) {
+ r.cancel();
+ }
+ }
} else {
- mRunningWindowAnim.end();
+ for (RunningWindowAnim r : mRunningWindowAnim) {
+ if (r != null) {
+ r.end();
+ }
+ }
}
}
if (mParallelRunningAnim != null) {
@@ -885,6 +918,9 @@
// Fast-finish the attaching animation if it's still running.
maybeUpdateRecentsAttachedState(false);
final GestureEndTarget endTarget = mGestureState.getEndTarget();
+ // Wait until the given View (if supplied) draws before resuming the last task.
+ View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
+
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
@@ -893,6 +929,7 @@
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
}
+
switch (endTarget) {
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
@@ -907,7 +944,12 @@
mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
break;
case LAST_TASK:
- mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ if (postResumeLastTask != null) {
+ ViewUtils.postFrameDrawn(postResumeLastTask,
+ () -> mStateCallback.setState(STATE_RESUME_LAST_TASK));
+ } else {
+ mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ }
TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
break;
}
@@ -1181,15 +1223,17 @@
createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
runningTaskTarget);
mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
- final RectFSpringAnim windowAnim;
+ final RectFSpringAnim[] windowAnim;
if (mIsSwipingPipToHome) {
mSwipePipToHomeAnimator = createWindowAnimationToPip(
homeAnimFactory, runningTaskTarget, start);
- windowAnim = mSwipePipToHomeAnimator;
+ mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
+ windowAnim = mSwipePipToHomeAnimators;
} else {
mSwipePipToHomeAnimator = null;
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
- windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+
+ windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsAnimationController == null) {
@@ -1203,15 +1247,22 @@
}
});
}
- windowAnim.start(mContext, velocityPxPerMs);
- mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+ mRunningWindowAnim = new RunningWindowAnim[windowAnim.length];
+ for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) {
+ RectFSpringAnim windowAnimation = windowAnim[i];
+ if (windowAnimation == null) {
+ continue;
+ }
+ windowAnimation.start(mContext, velocityPxPerMs);
+ mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
+ }
homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y);
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
mLauncherTransitionController = null;
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
- mTaskViewSimulator);
+ getRemoteTaskViewSimulators());
}
} else {
AnimatorSet animatorSet = new AnimatorSet();
@@ -1253,11 +1304,12 @@
animatorSet.play(windowAnim);
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(
- animatorSet, mGestureState.getEndTarget(), mTaskViewSimulator);
+ animatorSet, mGestureState.getEndTarget(),
+ getRemoteTaskViewSimulators());
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
animatorSet.start();
- mRunningWindowAnim = RunningWindowAnim.wrap(animatorSet);
+ mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)};
}
}
@@ -1272,16 +1324,21 @@
}
}
+ /**
+ * TODO(b/195473090) handle multiple task simulators (if needed) for PIP
+ */
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
// Directly animate the app to PiP (picture-in-picture) mode
final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
- final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
+ final RecentsOrientedState orientationState = mRemoteTargetHandles[0].mTaskViewSimulator
+ .getOrientationState();
final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
final int homeRotation = orientationState.getRecentsActivityRotation();
final Matrix homeToWindowPositionMap = new Matrix();
- final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
+ final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap,
+ startProgress)[0];
// Move the startRect to Launcher space as floatingIconView runs in Launcher
final Matrix windowToHomePositionMap = new Matrix();
homeToWindowPositionMap.invert(windowToHomePositionMap);
@@ -1310,7 +1367,7 @@
// is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
if (homeRotation == ROTATION_0
&& (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
- builder.setFromRotation(mTaskViewSimulator, windowRotation,
+ builder.setFromRotation(mRemoteTargetHandles[0].mTaskViewSimulator, windowRotation,
taskInfo.displayCutoutInsets);
}
final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
@@ -1340,7 +1397,7 @@
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
- setupWindowAnimation(swipePipToHomeAnimator);
+ setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator});
return swipePipToHomeAnimator;
}
@@ -1367,19 +1424,19 @@
* @param homeAnimationFactory The home animation factory.
*/
@Override
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
- RectFSpringAnim anim =
+ RectFSpringAnim[] anim =
super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
setupWindowAnimation(anim);
return anim;
}
- private void setupWindowAnimation(RectFSpringAnim anim) {
- anim.addOnUpdateListener((v, r, p) -> {
+ private void setupWindowAnimation(RectFSpringAnim[] anims) {
+ anims[0].addOnUpdateListener((v, r, p) -> {
updateSysUiFlags(Math.max(p, mCurrentShift.value));
});
- anim.addAnimatorListener(new AnimationSuccessListener() {
+ anims[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsView != null) {
@@ -1391,7 +1448,7 @@
}
});
if (mRecentsAnimationTargets != null) {
- mRecentsAnimationTargets.addReleaseCheck(anim);
+ mRecentsAnimationTargets.addReleaseCheck(anims[0]);
}
}
@@ -1639,7 +1696,7 @@
* if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
*/
private void maybeFinishSwipePipToHome() {
- if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
+ if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
mSwipePipToHomeAnimator.getComponentName(),
mSwipePipToHomeAnimator.getDestinationBounds(),
@@ -1680,8 +1737,8 @@
* depend on proper class initialization.
*/
protected void initAfterSubclassConstructor() {
- initTransitionEndpoints(
- mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
+ initTransitionEndpoints(mRemoteTargetHandles[0].mTaskViewSimulator
+ .getOrientationState().getLauncherDeviceProfile());
}
protected void performHapticFeedback() {
@@ -1698,7 +1755,8 @@
protected void linkRecentsViewScroll() {
SurfaceTransactionApplier.create(mRecentsView, applier -> {
- mTransformParams.setSyncTransactionApplier(applier);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+ .setSyncTransactionApplier(applier));
runOnRecentsAnimationStart(() ->
mRecentsAnimationTargets.addReleaseCheck(applier));
});
@@ -1824,19 +1882,25 @@
/**
* Applies the transform on the recents animation
*/
- protected void applyWindowTransform() {
- if (mWindowTransitionController != null) {
- mWindowTransitionController.setProgress(
- Math.max(mCurrentShift.value, getScaleProgressDueToScroll()),
- mDragLengthFactor);
- }
+ protected void applyScrollAndTransform() {
// No need to apply any transform if there is ongoing swipe-pip-to-home animator since
// that animator handles the leash solely.
- if (mRecentsAnimationTargets != null && !mIsSwipingPipToHome) {
- if (mRecentsViewScrollLinked && mRecentsView != null) {
- mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
+ boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
+ for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
+ AnimatorControllerWithResistance playbackController = remoteHandle.mPlaybackController;
+ if (playbackController != null) {
+ playbackController.setProgress(Math.max(mCurrentShift.value,
+ getScaleProgressDueToScroll()), mDragLengthFactor);
}
- mTaskViewSimulator.apply(mTransformParams);
+
+ if (notSwipingPipToHome) {
+ TaskViewSimulator taskViewSimulator = remoteHandle.mTaskViewSimulator;
+ if (setRecentsScroll) {
+ taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ }
+ taskViewSimulator.apply(remoteHandle.mTransformParams);
+ }
}
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
@@ -1891,7 +1955,6 @@
}
public interface Factory {
-
AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
}
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 624ade2..e781160 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -40,6 +40,7 @@
import android.os.Build;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -52,6 +53,7 @@
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -201,10 +203,36 @@
}
/**
+ * Sets the task size in {@param outRect} taking split screened windows into account.
+ * We assume combined height of both tasks will be same as one normal task, then we'll modify
+ * the task height/width based on the ratio of task screen space bounds from
+ * {@param splitInfo}
+ *
+ * @param desiredStageBounds whether task size for top/left or bottom/right needs to be computed
+ */
+ public final void calculateStagedSplitTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ SplitConfigurationOptions.StagedSplitBounds splitInfo,
+ @SplitConfigurationOptions.StagePosition int desiredStageBounds) {
+ calculateTaskSize(context, dp, outRect);
+
+ // TODO(b/181705607) Change for landscape vs portrait
+ float totalHeight = splitInfo.mLeftTopBounds.height()
+ + splitInfo.mRightBottomBounds.height()
+ + splitInfo.mDividerBounds.height() / 2f;
+ float topTaskPercent = splitInfo.mLeftTopBounds.height() / totalHeight;
+ if (desiredStageBounds == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+ float diff = outRect.height() * (1f - topTaskPercent);
+ outRect.bottom -= diff;
+ } else {
+ float diff = outRect.height() * topTaskPercent;
+ outRect.top += diff;
+ }
+ }
+
+ /**
* Calculates the taskView size for the provided device configuration.
*/
- public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
- PagedOrientationHandler orientedState) {
+ public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
Resources res = context.getResources();
if (dp.overviewShowAsGrid) {
Rect gridRect = new Rect();
@@ -262,14 +290,14 @@
public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
if (dp.isMultiWindowMode) {
WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
- if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ if (TaskView.clipStatusAndNavBars(dp)) {
out.x = bounds.availableSize.x;
out.y = bounds.availableSize.y;
} else {
out.x = bounds.availableSize.x + bounds.insets.left + bounds.insets.right;
out.y = bounds.availableSize.y + bounds.insets.top + bounds.insets.bottom;
}
- } else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ } else if (TaskView.clipStatusAndNavBars(dp)) {
out.x = dp.availableWidthPx;
out.y = dp.availableHeightPx;
} else {
@@ -385,6 +413,15 @@
*/
public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
+ /**
+ * Called when the animation to the target has finished, but right before updating the state.
+ * @return A View that needs to draw before ending the recents animation to LAST_TASK.
+ * (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
+ */
+ public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
+ return null;
+ }
+
public interface AnimationFactory {
void createActivityInterface(long transitionLength);
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 7fb8e16..4df1aad 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -57,7 +57,7 @@
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
- calculateTaskSize(context, dp, outRect, orientationHandler);
+ calculateTaskSize(context, dp, outRect);
if (dp.isVerticalBarLayout()
&& SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index e2f198c..773817f 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -28,6 +28,7 @@
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -44,6 +45,7 @@
import android.os.Messenger;
import android.os.ParcelUuid;
import android.os.UserHandle;
+import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -56,6 +58,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.AppCloseConfig;
@@ -101,7 +104,9 @@
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
if (mRunningOverHome) {
- mTransformParams.setHomeBuilderProxy(this::updateHomeActivityTransformDuringSwipeUp);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+ FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp));
}
}
@@ -109,7 +114,8 @@
protected void initTransitionEndpoints(DeviceProfile dp) {
super.initTransitionEndpoints(dp);
if (mRunningOverHome) {
- mMaxLauncherScale = 1 / mTaskViewSimulator.getFullScreenScale();
+ // Full screen scale should be independent of remote target handle
+ mMaxLauncherScale = 1 / mRemoteTargetHandles[0].mTaskViewSimulator.getFullScreenScale();
}
}
@@ -134,6 +140,10 @@
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
Intent intent = new Intent(mGestureState.getHomeIntent());
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME,
+ "createHomeAnimationFactory: " + intent.toShortString(true, true, true, false));
+ }
mActiveAnimationFactory.addGestureContract(intent);
try {
mContext.startActivity(intent, options.toBundle());
@@ -174,7 +184,8 @@
protected void notifyGestureAnimationStartToRecents() {
if (mRunningOverHome) {
if (SysUINavigationMode.getMode(mContext).hasGestures) {
- mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
+ mRecentsView.onGestureAnimationStartOnHome(
+ new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()});
}
} else {
super.notifyGestureAnimationStartToRecents();
@@ -202,19 +213,24 @@
mHomeAlpha = new AnimatedFloat();
mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
mVerticalShiftForScale.value = mCurrentShift.value;
- mTransformParams.setHomeBuilderProxy(
- this::updateHomeActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateHomeActivityTransformDuringHomeAnim));
} else {
mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
mHomeAlpha.value = 0;
-
- mHomeAlphaParams.setHomeBuilderProxy(
- this::updateHomeActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateHomeActivityTransformDuringHomeAnim));
}
mRecentsAlpha.value = 1;
- mTransformParams.setBaseBuilderProxy(
- this::updateRecentsActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateRecentsActivityTransformDuringHomeAnim));
}
@NonNull
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index d91d5b0..ae6ea79 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -25,10 +25,12 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -71,7 +73,7 @@
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
- calculateTaskSize(context, dp, outRect, orientationHandler);
+ calculateTaskSize(context, dp, outRect);
if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
} else {
@@ -131,6 +133,18 @@
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio, LINEAR);
+ pa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ activity.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ // Launcher's ScrimView will draw the background throughout the gesture.
+ taskbarUIController.forceHideBackground(true);
+ }
+ }
+ });
+
}
};
@@ -288,6 +302,10 @@
} else {
om.hideOverlay(150);
}
+ LauncherTaskbarUIController taskbarController = getTaskbarController();
+ if (taskbarController != null) {
+ taskbarController.hideEdu();
+ }
}
@Override
@@ -354,4 +372,16 @@
return NORMAL;
}
}
+
+ @Override
+ public View onSettledOnEndTarget(@Nullable GestureEndTarget endTarget) {
+ View superRet = super.onSettledOnEndTarget(endTarget);
+ LauncherTaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ // Start drawing taskbar's background again since launcher might stop drawing.
+ taskbarUIController.forceHideBackground(false);
+ return taskbarUIController.getRootView();
+ }
+ return superRet;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 3239b00..ce3406c 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -110,7 +110,7 @@
mActivity.setHintUserWillBeActive();
}
- if (!canUseWorkspaceView || appCanEnterPip) {
+ if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) {
return new LauncherHomeAnimationFactory();
}
if (workspaceView instanceof LauncherAppWidgetHostView) {
@@ -181,14 +181,16 @@
final float floatingWidgetAlpha = isTargetTranslucent ? 0 : 1;
RectF backgroundLocation = new RectF();
Rect crop = new Rect();
- mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
+ // We can assume there is only one remote target here because staged split never animates
+ // into the app icon, only into the homescreen
+ mRemoteTargetHandles[0].mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
Size windowSize = new Size(crop.width(), crop.height());
int fallbackBackgroundColor =
FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
hostView, backgroundLocation, windowSize,
- mTaskViewSimulator.getCurrentCornerRadius(), isTargetTranslucent,
- fallbackBackgroundColor);
+ mRemoteTargetHandles[0].mTaskViewSimulator.getCurrentCornerRadius(),
+ isTargetTranslucent, fallbackBackgroundColor);
return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 239233b..3c05a3e 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -16,20 +16,24 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.graphics.Rect;
import android.util.ArraySet;
+import android.util.Log;
import android.view.RemoteAnimationTarget;
import androidx.annotation.BinderThread;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.util.Arrays;
import java.util.Set;
/**
@@ -93,8 +97,19 @@
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "RecentsAnimationCallbacks.onAnimationStart");
+ }
+ // Convert appTargets to type RemoteAnimationTarget for all apps except Home app
+ RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets)
+ .filter(remoteAnimationTarget ->
+ remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME)
+ .map(RemoteAnimationTargetCompat::unwrap)
+ .toArray(RemoteAnimationTarget[]::new);
+
RemoteAnimationTarget[] nonAppTargets =
- mSystemUiProxy.onGoingToRecentsLegacy(mCancelled);
+ mSystemUiProxy.onGoingToRecentsLegacy(mCancelled, nonHomeApps);
+
RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
homeContentInsets, minimizedHomeBounds);
@@ -106,6 +121,10 @@
mController::finishAnimationToApp);
} else {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME,
+ "RecentsAnimationCallbacks.onAnimationStart callback");
+ }
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationStart(mController, targets);
}
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index c032889..b20d488 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -77,8 +77,12 @@
* Gets the navigation bar remote animation target if exists.
*/
public RemoteAnimationTargetCompat getNavBarRemoteAnimationTarget() {
+ return getNonAppTargetOfType(TYPE_NAVIGATION_BAR);
+ }
+
+ public RemoteAnimationTargetCompat getNonAppTargetOfType(int type) {
for (RemoteAnimationTargetCompat target : nonApps) {
- if (target.windowType == TYPE_NAVIGATION_BAR) {
+ if (target.windowType == type) {
return target;
}
}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 4495455..ec51599 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -15,9 +15,13 @@
*/
package com.android.quickstep;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT;
import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
import android.content.Context;
@@ -36,8 +40,11 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.AppCloseConfig;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RectFSpringAnim2;
import com.android.quickstep.util.TaskViewSimulator;
@@ -46,7 +53,11 @@
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
-public abstract class SwipeUpAnimationLogic {
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public abstract class SwipeUpAnimationLogic implements
+ RecentsAnimationCallbacks.RecentsAnimationListener{
protected static final Rect TEMP_RECT = new Rect();
@@ -55,9 +66,9 @@
protected final Context mContext;
protected final RecentsAnimationDeviceState mDeviceState;
protected final GestureState mGestureState;
- protected final TaskViewSimulator mTaskViewSimulator;
- protected final TransformParams mTransformParams;
+ protected final RemoteTargetHandle[] mRemoteTargetHandles;
+ protected SplitConfigurationOptions.StagedSplitBounds mStagedSplitBounds;
// Shift in the range of [0, 1].
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
@@ -70,37 +81,56 @@
// How much further we can drag past recents, as a factor of mTransitionDragLength.
protected float mDragLengthFactor = 1;
- protected AnimatorControllerWithResistance mWindowTransitionController;
+ protected boolean mIsSwipeForStagedSplit;
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState, TransformParams transformParams) {
mContext = context;
mDeviceState = deviceState;
mGestureState = gestureState;
- mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
- mTransformParams = transformParams;
- mTaskViewSimulator.getOrientationState().update(
+ mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds().length > 1;
+
+ TaskViewSimulator primaryTVS = new TaskViewSimulator(context,
+ gestureState.getActivityInterface());
+ primaryTVS.getOrientationState().update(
mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
mDeviceState.getRotationTouchHelper().getDisplayRotation());
+ mRemoteTargetHandles = new RemoteTargetHandle[mIsSwipeForStagedSplit ? 2 : 1];
+ mRemoteTargetHandles[0] = new RemoteTargetHandle(primaryTVS, transformParams);
+
+ if (mIsSwipeForStagedSplit) {
+ TaskViewSimulator secondaryTVS = new TaskViewSimulator(context,
+ gestureState.getActivityInterface());
+ secondaryTVS.getOrientationState().update(
+ mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+ mDeviceState.getRotationTouchHelper().getDisplayRotation());
+ mRemoteTargetHandles[1] = new RemoteTargetHandle(secondaryTVS, new TransformParams());
+ }
}
protected void initTransitionEndpoints(DeviceProfile dp) {
mDp = dp;
-
- mTaskViewSimulator.setDp(dp);
mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
- dp, mContext, TEMP_RECT,
- mTaskViewSimulator.getOrientationState().getOrientationHandler());
+ dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].mTaskViewSimulator
+ .getOrientationState().getOrientationHandler());
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
- PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
- mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
- AnimatorPlaybackController normalController = pa.createPlaybackController();
- mWindowTransitionController = AnimatorControllerWithResistance.createForRecents(
- normalController, mContext, mTaskViewSimulator.getOrientationState(),
- mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
- mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE);
+ for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
+ PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
+ TaskViewSimulator taskViewSimulator = remoteHandle.mTaskViewSimulator;
+ taskViewSimulator.setDp(dp);
+ taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR);
+ AnimatorPlaybackController playbackController =
+ pendingAnimation.createPlaybackController();
+
+ remoteHandle.mPlaybackController = AnimatorControllerWithResistance.createForRecents(
+ playbackController, mContext, taskViewSimulator.getOrientationState(),
+ mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
+ taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE
+ );
+ }
}
@UiThread
@@ -125,7 +155,9 @@
public abstract void updateFinalShift();
protected PagedOrientationHandler getOrientationHandler() {
- return mTaskViewSimulator.getOrientationState().getOrientationHandler();
+ // OrientationHandler should be independent of remote target, can directly take one
+ return mRemoteTargetHandles[0].mTaskViewSimulator
+ .getOrientationState().getOrientationHandler();
}
protected abstract class HomeAnimationFactory {
@@ -207,31 +239,102 @@
* @param startProgress The progress of {@link #mCurrentShift} to start thw window from.
* @return {@link RectF} represents the bounds as starting point in window space.
*/
- protected RectF updateProgressForStartRect(Matrix outMatrix, float startProgress) {
+ protected RectF[] updateProgressForStartRect(Matrix outMatrix, float startProgress) {
mCurrentShift.updateValue(startProgress);
- mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
- RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+ RectF[] startRects = new RectF[mRemoteTargetHandles.length];
+ for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
+ i < mRemoteTargetHandlesLength; i++) {
+ RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
+ TaskViewSimulator tvs = remoteHandle.mTaskViewSimulator;
+ tvs.apply(remoteHandle.mTransformParams.setProgress(startProgress));
- mTaskViewSimulator.applyWindowToHomeRotation(outMatrix);
-
- final RectF startRect = new RectF(cropRectF);
- mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
- return startRect;
+ startRects[i] = new RectF(tvs.getCurrentCropRect());
+ tvs.applyWindowToHomeRotation(outMatrix);
+ tvs.getCurrentMatrix().mapRect(startRects[i]);
+ }
+ return startRects;
}
+ /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
+ protected void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
+ for (RemoteTargetHandle handle : mRemoteTargetHandles) {
+ consumer.accept(handle);
+ }
+ }
+
+ /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */
+ protected TaskViewSimulator[] getRemoteTaskViewSimulators() {
+ return Arrays.stream(mRemoteTargetHandles)
+ .map(remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator)
+ .toArray(TaskViewSimulator[]::new);
+ }
+
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+ RemoteAnimationTargetCompat dividerTarget = targets.getNonAppTargetOfType(
+ TYPE_DOCK_DIVIDER);
+ RemoteAnimationTargetCompat primaryTaskTarget;
+ RemoteAnimationTargetCompat secondaryTaskTarget;
+
+ if (!mIsSwipeForStagedSplit) {
+ primaryTaskTarget = targets.findTask(mGestureState.getRunningTaskId());
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
+
+ if (primaryTaskTarget != null) {
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget);
+ }
+ } else {
+ // We're in staged split
+ primaryTaskTarget = targets.apps[0];
+ secondaryTaskTarget = targets.apps[1];
+ mStagedSplitBounds = new SplitConfigurationOptions.StagedSplitBounds(
+ primaryTaskTarget.screenSpaceBounds,
+ secondaryTaskTarget.screenSpaceBounds, dividerTarget.screenSpaceBounds);
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
+ mStagedSplitBounds);
+ mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget,
+ mStagedSplitBounds);
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(primaryTaskTarget));
+ mRemoteTargetHandles[1].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(secondaryTaskTarget));
+ }
+ }
+
+ private RemoteAnimationTargets createRemoteAnimationTargetsForTarget(
+ RemoteAnimationTargetCompat target) {
+ return new RemoteAnimationTargets(new RemoteAnimationTargetCompat[]{target},
+ null, null, MODE_CLOSING);
+ }
/**
* Creates an animation that transforms the current app window into the home app.
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
* @param homeAnimationFactory The home animation factory.
*/
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
+ // TODO(b/195473584) compute separate end targets for different staged split
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
-
+ RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length];
Matrix homeToWindowPositionMap = new Matrix();
- final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
- RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+ RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
+ for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
+ i < mRemoteTargetHandlesLength; i++) {
+ RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
+ out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
+ targetRect, remoteHandle.mTransformParams, remoteHandle.mTaskViewSimulator,
+ startRects[i], homeToWindowPositionMap);
+ }
+ return out;
+ }
+ private RectFSpringAnim getWindowAnimationToHomeInternal(
+ HomeAnimationFactory homeAnimationFactory, RectF targetRect,
+ TransformParams transformParams, TaskViewSimulator taskViewSimulator,
+ RectF startRect, Matrix homeToWindowPositionMap) {
+ RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
// Move the startRect to Launcher space as floatingIconView runs in Launcher
Matrix windowToHomePositionMap = new Matrix();
homeToWindowPositionMap.invert(windowToHomePositionMap);
@@ -240,7 +343,7 @@
RectFSpringAnim anim;
if (PROTOTYPE_APP_CLOSE.get()) {
anim = new RectFSpringAnim2(startRect, targetRect, mContext,
- mTaskViewSimulator.getCurrentCornerRadius(),
+ taskViewSimulator.getCurrentCornerRadius(),
homeAnimationFactory.getEndRadius(cropRectF));
} else {
anim = new RectFSpringAnim(startRect, targetRect, mContext);
@@ -248,9 +351,10 @@
homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
- homeAnimationFactory, cropRectF, homeToWindowPositionMap);
- anim.addOnUpdateListener(runner);
+ homeAnimationFactory, cropRectF, homeToWindowPositionMap,
+ transformParams, taskViewSimulator);
anim.addAnimatorListener(runner);
+ anim.addOnUpdateListener(runner);
return anim;
}
@@ -262,6 +366,7 @@
final RectF mWindowCurrentRect = new RectF();
final Matrix mHomeToWindowPositionMap;
+ private final TransformParams mLocalTransformParams;
final HomeAnimationFactory mAnimationFactory;
final AnimatorPlaybackController mHomeAnim;
@@ -271,17 +376,19 @@
final float mEndRadius;
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
- Matrix homeToWindowPositionMap) {
+ Matrix homeToWindowPositionMap, TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator) {
mAnimationFactory = factory;
mHomeAnim = factory.createActivityAnimationToHome();
mCropRectF = cropRectF;
mHomeToWindowPositionMap = homeToWindowPositionMap;
+ mLocalTransformParams = transformParams;
cropRectF.roundOut(mCropRect);
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
// rounding at the end of the animation.
- mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
+ mStartRadius = taskViewSimulator.getCurrentCornerRadius();
mEndRadius = factory.getEndRadius(cropRectF);
}
@@ -300,10 +407,11 @@
if (mAnimationFactory.keepWindowOpaque()) {
alpha = 1f;
}
- mTransformParams
+ mLocalTransformParams
.setTargetAlpha(alpha)
.setCornerRadius(cornerRadius);
- mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
+ mLocalTransformParams.applySurfaceParams(mLocalTransformParams
+ .createSurfaceParams(this));
mAnimationFactory.update(config, currentRect, progress,
mMatrix.mapRadius(cornerRadius));
}
@@ -332,6 +440,21 @@
}
}
+ /**
+ * Container to keep together all the associated objects whose properties need to be updated to
+ * animate a single remote app target
+ */
+ public static class RemoteTargetHandle {
+ public TaskViewSimulator mTaskViewSimulator;
+ public TransformParams mTransformParams;
+ public AnimatorControllerWithResistance mPlaybackController;
+ public RemoteTargetHandle(TaskViewSimulator taskViewSimulator,
+ TransformParams transformParams) {
+ mTransformParams = transformParams;
+ mTaskViewSimulator = taskViewSimulator;
+ }
+ }
+
public interface RunningWindowAnim {
void end();
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 11ca4b1..7d2d413 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
@@ -632,10 +633,11 @@
* @param cancel true if recents starting is being cancelled.
* @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
*/
- public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) {
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
if (mSplitScreen != null) {
try {
- return mSplitScreen.onGoingToRecentsLegacy(cancel);
+ return mSplitScreen.onGoingToRecentsLegacy(cancel, apps);
} catch (RemoteException e) {
Log.w(TAG, "Failed call onGoingToRecentsLegacy");
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index b5da097..5b9e214 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -154,9 +154,10 @@
boolean isRunningTask = v.isRunningTask();
TransformParams params = null;
TaskViewSimulator tsv = null;
+ // TODO(b/195675206) handle two TSVs here
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
- params = v.getRecentsView().getLiveTileParams();
- tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
+ params = v.getRecentsView().getRemoteTargetHandles()[0].mTransformParams;
+ tsv = v.getRecentsView().getRemoteTargetHandles()[0].mTaskViewSimulator;
}
createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets,
depthController, out, params, tsv);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4979206..20eff34 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -99,6 +99,7 @@
import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.SplitScreenBounds;
@@ -364,6 +365,7 @@
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
ProtoTracer.INSTANCE.get(this).add(this);
+ LauncherSplitScreenListener.INSTANCE.get(this).init();
sConnected = true;
}
@@ -520,6 +522,7 @@
getSystemService(AccessibilityManager.class)
.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
+ LauncherSplitScreenListener.INSTANCE.get(this).destroy();
mTaskbarManager.destroy();
sConnected = false;
super.onDestroy();
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 64a428f..765480c 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -79,8 +79,10 @@
* to the home task. This allows us to handle quick-switch similarly to a quick-switching
* from a foreground task.
*/
- public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
- mHomeTaskInfo = homeTaskInfo;
+ public void onGestureAnimationStartOnHome(RunningTaskInfo[] homeTaskInfo) {
+ // TODO(b/195607777) General fallback love, but this might be correct
+ // Home task should be defined as the front-most task info I think?
+ mHomeTaskInfo = homeTaskInfo[0];
onGestureAnimationStart(homeTaskInfo);
}
@@ -92,8 +94,8 @@
@Override
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
- TaskViewSimulator taskViewSimulator) {
- super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulator);
+ TaskViewSimulator[] taskViewSimulators) {
+ super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulators);
if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
TaskView tv = getTaskViewByTaskId(mHomeTaskInfo.taskId);
if (tv != null) {
@@ -133,7 +135,13 @@
}
@Override
- protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
+ protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
+ if (runningTaskInfos.length > 1) {
+ // can't be in split screen w/ home task
+ return super.shouldAddStubTaskView(runningTaskInfos);
+ }
+
+ RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
if (mHomeTaskInfo != null && runningTaskInfo != null &&
mHomeTaskInfo.taskId == runningTaskInfo.taskId
&& getTaskViewCount() == 0) {
@@ -141,7 +149,7 @@
// show the empty recents message instead of showing a stub task and later removing it.
return false;
}
- return super.shouldAddStubTaskView(runningTaskInfo);
+ return super.shouldAddStubTaskView(runningTaskInfos);
}
@Override
@@ -149,6 +157,7 @@
// When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
+ // TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
int runningTaskId = getTaskIdsForRunningTaskView()[0];
if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId && !tasks.isEmpty()) {
// Check if the task list has running task
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index b2183d6..04b147c 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -46,6 +46,7 @@
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.GestureState;
import com.android.quickstep.OverviewComponentObserver;
@@ -263,14 +264,16 @@
void initDp(DeviceProfile dp) {
initTransitionEndpoints(dp);
- mTaskViewSimulator.setPreviewBounds(
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreviewBounds(
new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
}
@Override
public void updateFinalShift() {
- mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
- mTaskViewSimulator.apply(mTransformParams);
+ mRemoteTargetHandles[0].mPlaybackController
+ .setProgress(mCurrentShift.value, mDragLengthFactor);
+ mRemoteTargetHandles[0].mTaskViewSimulator.apply(
+ mRemoteTargetHandles[0].mTransformParams);
}
AnimatedFloat getCurrentShift() {
@@ -326,7 +329,8 @@
mFakeIconView.setVisibility(View.INVISIBLE);
}
};
- RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
+ RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift,
+ homeAnimFactory)[0];
windowAnim.start(mContext, velocityPxPerMs);
return windowAnim;
}
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index baca76c..7c83833 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -158,8 +158,7 @@
Rect startRect = new Rect();
PagedOrientationHandler orientationHandler = params.recentsOrientedState
.getOrientationHandler();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
- orientationHandler);
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect);
long distanceToCover = startRect.bottom;
PendingAnimation resistAnim = params.resistAnim != null
? params.resistAnim
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
new file mode 100644
index 0000000..da665d4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
@@ -0,0 +1,104 @@
+package com.android.quickstep.util;
+
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+
+import android.content.Context;
+import android.os.IBinder;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.launcher3.util.SplitConfigurationOptions.StageType;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
+import com.android.quickstep.SystemUiProxy;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+
+/**
+ * Listeners for system wide split screen position and stage changes.
+ * Use {@link #getSplitTaskIds()} to determine which tasks, if any, are in staged split.
+ */
+public class LauncherSplitScreenListener extends ISplitScreenListener.Stub {
+
+ public static final MainThreadInitializedObject<LauncherSplitScreenListener> INSTANCE =
+ new MainThreadInitializedObject<>(LauncherSplitScreenListener::new);
+
+ private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
+ private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
+
+ public LauncherSplitScreenListener(Context context) {
+ mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
+ mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
+ }
+
+ /** Also call {@link #destroy()} when done. */
+ public void init() {
+ SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this);
+ }
+
+ public void destroy() {
+ SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this);
+ }
+
+ /**
+ * @return index 0 will be task in left/top position, index 1 in right/bottom position.
+ * Will return empty array if device is not in staged split
+ */
+ public int[] getSplitTaskIds() {
+ if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
+ return new int[]{};
+ }
+ int[] out = new int[2];
+ if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ out[0] = mMainStagePosition.taskId;
+ out[1] = mSideStagePosition.taskId;
+ } else {
+ out[1] = mMainStagePosition.taskId;
+ out[0] = mSideStagePosition.taskId;
+ }
+ return out;
+ }
+
+ @Override
+ public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+ mMainStagePosition.stagePosition = position;
+ } else {
+ mSideStagePosition.stagePosition = position;
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
+ // If task is not visible but we are tracking it, stop tracking it
+ if (!visible) {
+ if (mMainStagePosition.taskId == taskId) {
+ resetTaskId(mMainStagePosition);
+ } else if (mSideStagePosition.taskId == taskId) {
+ resetTaskId(mSideStagePosition);
+ } // else it's an un-tracked child
+ return;
+ }
+
+ // If stage has moved to undefined, stop tracking the task
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
+ resetTaskId(taskId == mMainStagePosition.taskId ?
+ mMainStagePosition : mSideStagePosition);
+ return;
+ }
+
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+ mMainStagePosition.taskId = taskId;
+ } else {
+ mSideStagePosition.taskId = taskId;
+ }
+ }
+
+ private void resetTaskId(StagedSplitTaskPosition taskPosition) {
+ taskPosition.taskId = -1;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index ea1ece8..d0fb9e5 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -15,10 +15,15 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.Utilities.comp;
+
+import android.annotation.Nullable;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
+import com.android.launcher3.util.HorizontalInsettableView;
import com.android.unfold.UnfoldTransitionProgressProvider;
import com.android.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
@@ -27,10 +32,17 @@
*/
public class LauncherUnfoldAnimationController {
+ // Percentage of the width of the quick search bar that will be reduced
+ // from the both sides of the bar when progress is 0
+ private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
+
private final Launcher mLauncher;
private final UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
private final UnfoldMoveFromCenterWorkspaceAnimator mMoveFromCenterWorkspaceAnimation;
+ @Nullable
+ private HorizontalInsettableView mQsbInsettable;
+
private final AnimationListener mAnimationListener = new AnimationListener();
private boolean mIsTransitionRunning = false;
@@ -51,6 +63,11 @@
* Called when launcher is resumed
*/
public void onResume() {
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat != null && hotseat.getQsb() instanceof HorizontalInsettableView) {
+ mQsbInsettable = (HorizontalInsettableView) hotseat.getQsb();
+ }
+
final ViewTreeObserver obs = mLauncher.getWorkspace().getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -68,12 +85,13 @@
* Called when launcher activity is paused
*/
public void onPause() {
- mIsReadyToPlayAnimation = false;
-
if (mIsTransitionRunning) {
mIsTransitionRunning = false;
- mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
+ mAnimationListener.onTransitionFinished();
}
+
+ mIsReadyToPlayAnimation = false;
+ mQsbInsettable = null;
}
/**
@@ -109,6 +127,10 @@
public void onTransitionFinished() {
if (mIsReadyToPlayAnimation) {
mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
+
+ if (mQsbInsettable != null) {
+ mQsbInsettable.setHorizontalInsets(0);
+ }
}
mIsTransitionRunning = false;
@@ -117,6 +139,11 @@
@Override
public void onTransitionProgress(float progress) {
mMoveFromCenterWorkspaceAnimation.onTransitionProgress(progress);
+
+ if (mQsbInsettable != null) {
+ float insetPercentage = comp(progress) * MAX_WIDTH_INSET_FRACTION;
+ mQsbInsettable.setHorizontalInsets(insetPercentage);
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 8834dc2..302526d 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -42,8 +42,7 @@
PagedOrientationHandler orientationHandler) {
// Track the bottom of the window.
Rect taskSize = new Rect();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(
- context, dp, taskSize, orientationHandler);
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize);
return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 7cfd151..21e0ae8 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -396,7 +396,7 @@
Rect insets = dp.getInsets();
float fullWidth = dp.widthPx;
float fullHeight = dp.heightPx;
- if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ if (TaskView.clipStatusAndNavBars(dp)) {
fullWidth -= insets.left + insets.right;
fullHeight -= insets.top + insets.bottom;
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 7eee415..7b1c62e 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.states.RotationHelper.deltaRotation;
import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
+import static com.android.launcher3.util.SplitConfigurationOptions.*;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
@@ -29,6 +30,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Log;
import androidx.annotation.NonNull;
@@ -49,8 +51,13 @@
*/
public class TaskViewSimulator implements TransformParams.BuilderProxy {
+ private final String TAG = "TaskViewSimulator";
+ private final boolean DEBUG = false;
+
private final Rect mTmpCropRect = new Rect();
private final RectF mTempRectF = new RectF();
+ // Additional offset for split tasks
+ private final Point mSplitOffset = new Point();
private final float[] mTempPoint = new float[2];
private final Context mContext;
@@ -63,6 +70,8 @@
private final Rect mTaskRect = new Rect();
private final PointF mPivot = new PointF();
private DeviceProfile mDp;
+ @StagePosition
+ private int mStagePosition = STAGE_POSITION_UNDEFINED;
private final Matrix mMatrix = new Matrix();
private final Matrix mMatrixTmp = new Matrix();
@@ -89,6 +98,7 @@
// Cached calculations
private boolean mLayoutValid = false;
private int mOrientationStateId;
+ private StagedSplitBounds mStagedSplitBounds;
public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
mContext = context;
@@ -128,9 +138,19 @@
if (mDp == null) {
return 1;
}
- mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
- mOrientationState.getOrientationHandler());
- return mOrientationState.getFullScreenScaleAndPivot(mTaskRect, mDp, mPivot);
+ Rect fullTaskSize = new Rect();
+ mSizeStrategy.calculateTaskSize(mContext, mDp, fullTaskSize);
+
+ if (mStagedSplitBounds != null) {
+ // The task rect changes according to the staged split task sizes, but recents
+ // fullscreen scale and pivot remains the same since the task fits into the existing
+ // sized task space bounds
+ mSizeStrategy.calculateStagedSplitTaskSize(mContext, mDp, mTaskRect, mStagedSplitBounds,
+ mStagePosition);
+ } else {
+ mTaskRect.set(fullTaskSize);
+ }
+ return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
}
/**
@@ -143,6 +163,24 @@
}
/**
+ * Sets the targets which the simulator will control specifically for targets to animate when
+ * in split screen
+ *
+ * @param splitInfo set to {@code null} when not in staged split mode
+ */
+ public void setPreview(RemoteAnimationTargetCompat runningTarget, StagedSplitBounds splitInfo) {
+ setPreview(runningTarget);
+ mStagedSplitBounds = splitInfo;
+ if (mStagedSplitBounds == null) {
+ mStagePosition = STAGE_POSITION_UNDEFINED;
+ return;
+ }
+ mStagePosition = mThumbnailPosition.equals(splitInfo.mLeftTopBounds) ?
+ STAGE_POSITION_TOP_OR_LEFT :
+ STAGE_POSITION_BOTTOM_OR_RIGHT;
+ }
+
+ /**
* Sets the targets which the simulator will control
*/
public void setPreviewBounds(Rect bounds, Rect insets) {
@@ -239,6 +277,15 @@
getFullScreenScale();
mThumbnailData.rotation = mOrientationState.getDisplayRotation();
+ // TODO(b/195145340) handle non 50-50 split scenarios
+ if (mStagedSplitBounds != null) {
+ if (mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ // The preview set is for the bottom/right, inset by top/left task
+ mSplitOffset.y = mStagedSplitBounds.mLeftTopBounds.height() +
+ mStagedSplitBounds.mDividerBounds.height() / 2;
+ }
+ }
+
// mIsRecentsRtl is the inverse of TaskView RTL.
boolean isRtlEnabled = !mIsRecentsRtl;
mPositionHelper.updateThumbnailMatrix(
@@ -246,6 +293,9 @@
mTaskRect.width(), mTaskRect.height(),
mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
mPositionHelper.getMatrix().invert(mInversePositionMatrix);
+ if (DEBUG) {
+ Log.d(TAG, " taskRect: " + mTaskRect + " splitOffset: " + mSplitOffset);
+ }
}
float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
@@ -280,6 +330,9 @@
recentsViewPrimaryTranslation.value);
applyWindowToHomeRotation(mMatrix);
+ // Move lower/right split window into correct position
+ mMatrix.postTranslate(0, mSplitOffset.y);
+
// Crop rect is the inverse of thumbnail matrix
mTempRectF.set(-insets.left, -insets.top,
taskWidth + insets.right, taskHeight + insets.bottom);
@@ -287,6 +340,25 @@
mTempRectF.roundOut(mTmpCropRect);
params.applySurfaceParams(params.createSurfaceParams(this));
+
+ if (!DEBUG) {
+ return;
+ }
+ Log.d(TAG, "progress: " + fullScreenProgress
+ + " scale: " + scale
+ + " recentsViewScale: " + recentsViewScale.value
+ + " crop: " + mTmpCropRect
+ + " radius: " + getCurrentCornerRadius()
+ + " translate: " + mSplitOffset
+ + " taskW: " + taskWidth + " H: " + taskHeight
+ + " taskRect: " + mTaskRect
+ + " taskPrimaryT: " + taskPrimaryTranslation.value
+ + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
+ + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
+ + " taskSecondaryT: " + taskSecondaryTranslation.value
+ + " recentsScroll: " + recentsViewScroll.value
+ + " pivot: " + mPivot
+ );
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
new file mode 100644
index 0000000..cd20f4b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -0,0 +1,137 @@
+package com.android.quickstep.views;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
+ *
+ * That's right. If you call within the next 5 minutes we'll go ahead and double your order and
+ * send you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House.
+ * And not only that, we'll even clean up your thumbnail request if you don't like it.
+ * All the benefits of one TaskView, except DOUBLED!
+ *
+ * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
+ */
+public class GroupedTaskView extends TaskView {
+
+ private Task mSecondaryTask;
+ private TaskThumbnailView mSnapshotView2;
+ private CancellableTask mThumbnailLoadRequest2;
+
+ public GroupedTaskView(Context context) {
+ super(context);
+ }
+
+ public GroupedTaskView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSnapshotView2 = findViewById(R.id.bottomright_snapshot);
+ }
+
+ public void bind(Task primary, Task secondary, RecentsOrientedState orientedState) {
+ super.bind(primary, orientedState);
+ mSecondaryTask = secondary;
+ mTaskIdContainer[1] = secondary.key.id;
+ mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2);
+ mSnapshotView2.bind(secondary);
+ adjustThumbnailBoundsForSplit();
+ }
+
+ @Override
+ public void onTaskListVisibilityChanged(boolean visible, int changes) {
+ super.onTaskListVisibilityChanged(visible, changes);
+ if (visible) {
+ RecentsModel model = RecentsModel.INSTANCE.get(getContext());
+ TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
+
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(mSecondaryTask,
+ thumbnailData -> mSnapshotView2.setThumbnail(
+ mSecondaryTask, thumbnailData
+ ));
+ }
+
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ // TODO What's the Icon for this going to look like? :o
+ }
+ } else {
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mSnapshotView2.setThumbnail(null, null);
+ // Reset the task thumbnail reference as well (it will be fetched from the cache or
+ // reloaded next time we need it)
+ mSecondaryTask.thumbnail = null;
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ // TODO
+ }
+ }
+ }
+
+ @Override
+ protected void cancelPendingLoadTasks() {
+ super.cancelPendingLoadTasks();
+ if (mThumbnailLoadRequest2 != null) {
+ mThumbnailLoadRequest2.cancel();
+ mThumbnailLoadRequest2 = null;
+ }
+ }
+
+ @Override
+ public void onRecycle() {
+ super.onRecycle();
+ mSnapshotView2.setThumbnail(mSecondaryTask, null);
+ }
+
+ @Override
+ public void setOverlayEnabled(boolean overlayEnabled) {
+ super.setOverlayEnabled(overlayEnabled);
+ mSnapshotView2.setOverlayEnabled(overlayEnabled);
+ }
+
+ private void adjustThumbnailBoundsForSplit() {
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
+ primaryLp.width = mSecondaryTask == null ?
+ MATCH_PARENT :
+ getWidth();
+ int spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ // TODO get divider height
+ int dividerBar = 20;
+ primaryLp.height = mSecondaryTask == null ?
+ MATCH_PARENT :
+ (getHeight() - spaceAboveSnapshot - dividerBar) / 2;
+ mSnapshotView.setLayoutParams(primaryLp);
+
+ if (mSecondaryTask == null) {
+ mSnapshotView2.setVisibility(GONE);
+ return;
+ }
+
+ mSnapshotView2.setVisibility(VISIBLE);
+ ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
+ secondaryLp.width = getWidth();
+ secondaryLp.height = primaryLp.height;
+ mSnapshotView2.setLayoutParams(secondaryLp);
+ mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d77c8ec..ef52b41 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -19,6 +19,7 @@
import static android.view.Surface.ROTATION_0;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
@@ -51,6 +52,7 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -143,11 +145,13 @@
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.SwipeUpAnimationLogic.RemoteTargetHandle;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitScreenBounds;
@@ -317,7 +321,13 @@
view.setScaleY(scale);
view.mLastComputedTaskStartPushOutDistance = null;
view.mLastComputedTaskEndPushOutDistance = null;
- view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
+ view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() {
+ @Override
+ public void accept(RemoteTargetHandle remoteTargetHandle) {
+ remoteTargetHandle.mTaskViewSimulator.recentsViewScale.value =
+ scale;
+ }
+ });
view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
view.updatePageOffsets();
}
@@ -365,8 +375,7 @@
// mTaskGridVerticalDiff and mTopBottomRowHeightDiff summed together provides the top
// position for bottom row of grid tasks.
- protected final TransformParams mLiveTileParams = new TransformParams();
- protected final TaskViewSimulator mLiveTileTaskViewSimulator;
+ protected RemoteTargetHandle[] mRemoteTargetHandles;
protected final Rect mLastComputedTaskSize = new Rect();
protected final Rect mLastComputedGridSize = new Rect();
protected final Rect mLastComputedGridTaskSize = new Rect();
@@ -402,9 +411,10 @@
private final InvariantDeviceProfile mIdp;
/**
- * Getting views should be done via {@link #getTaskViewFromPool()}
+ * Getting views should be done via {@link #getTaskViewFromPool(boolean)}
*/
private final ViewPool<TaskView> mTaskViewPool;
+ private final ViewPool<GroupedTaskView> mGroupedTaskViewPool;
private final TaskOverlayFactory mTaskOverlayFactory;
@@ -504,13 +514,13 @@
// Only valid until the launcher state changes to NORMAL
/**
* ID for the current running TaskView view, unique amongst TaskView instances. ID's are set
- * through {@link #getTaskViewFromPool()} and incremented by {@link #mTaskViewIdCount}
+ * through {@link #getTaskViewFromPool(boolean)} and incremented by {@link #mTaskViewIdCount}
*/
protected int mRunningTaskViewId = -1;
private int mTaskViewIdCount;
private final int[] INVALID_TASK_IDS = new int[]{-1, -1};
protected boolean mRunningTaskTileHidden;
- private Task mTmpRunningTask;
+ private Task[] mTmpRunningTasks;
protected int mFocusedTaskViewId = -1;
private boolean mTaskIconScaledDown = false;
@@ -626,6 +636,9 @@
mClearAllButton.setOnClickListener(this::dismissAllTasks);
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
10 /* initial size */);
+ // There's only one pair of grouped tasks we can envision at the moment
+ mGroupedTaskViewPool = new ViewPool<>(context, this,
+ R.layout.task_grouped, 2 /* max size */, 1 /* initial size */);
mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
@@ -656,10 +669,6 @@
// Initialize quickstep specific cache params here, as this is constructed only once
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
- mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
- mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
- mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
-
mTintingColor = getForegroundScrimDimColor(context);
}
@@ -707,7 +716,7 @@
super.dispatchDraw(canvas);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
- && mLiveTileParams.getTargetSet() != null) {
+ && mRemoteTargetHandles != null) {
redrawLiveTile();
}
}
@@ -749,9 +758,13 @@
if (mHandleTaskStackChanges) {
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
- Task task = taskView.getTask();
- taskView.getThumbnail().setThumbnail(task, thumbnailData);
- return task;
+ for (TaskView.TaskIdAttributeContainer container :
+ taskView.getTaskIdAttributeContainers()) {
+ if (container == null || taskId != container.getTask().key.id) {
+ continue;
+ }
+ container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);
+ }
}
}
return null;
@@ -812,7 +825,8 @@
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = new SurfaceTransactionApplier(this);
- mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+ .setSyncTransactionApplier(mSyncTransactionApplier));
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIPipAnimationListener.setActivityAndRecentsView(mActivity, this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
@@ -830,7 +844,8 @@
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = null;
- mLiveTileParams.setSyncTransactionApplier(null);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+ .setSyncTransactionApplier(null));
executeSideTaskLaunchCallback();
RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
@@ -851,9 +866,15 @@
if (child instanceof TaskView && child != mSplitHiddenTaskView
&& child != mMovingTaskView) {
TaskView taskView = (TaskView) child;
- mHasVisibleTaskData.delete(taskView.getTaskIds()[0]);
+ for (int i : taskView.getTaskIds()) {
+ mHasVisibleTaskData.delete(i);
+ }
+ if (child instanceof GroupedTaskView) {
+ mGroupedTaskViewPool.recycle((GroupedTaskView)taskView);
+ } else {
+ mTaskViewPool.recycle(taskView);
+ }
taskView.setTaskViewId(-1);
- mTaskViewPool.recycle(taskView);
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
updateTaskStartIndex(child);
@@ -906,10 +927,15 @@
}
}
+ /**
+ * TODO(b/195675206) Check both taskIDs from runningTaskViewId
+ * and launch if either of them is {@param taskId}
+ */
public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
int runningTaskViewId = getTaskViewIdFromTaskId(taskId);
if (mRunningTaskViewId != -1 && mRunningTaskViewId == runningTaskViewId) {
- RemoteAnimationTargets targets = getLiveTileParams().getTargetSet();
+ TransformParams params = mRemoteTargetHandles[0].mTransformParams;
+ RemoteAnimationTargets targets = params.getTargetSet();
if (targets != null && targets.findTask(taskId) != null) {
launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
targets.nonApps);
@@ -1015,10 +1041,21 @@
if (!enabled) {
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
- mTmpRunningTask = null;
+ mTmpRunningTasks = null;
if (mSplitSelectStateController.isSplitSelectActive()) {
cancelSplitSelect(false);
}
+ // Remove grouped tasks and recycle once we exit overview
+ int taskCount = getTaskViewCount();
+ for (int i = 0; i < taskCount; i++) {
+ View v = getTaskViewAt(i);
+ if (!(v instanceof GroupedTaskView)) {
+ return;
+ }
+ GroupedTaskView gtv = (GroupedTaskView) v;
+ gtv.onTaskListVisibilityChanged(false);
+ removeView(gtv);
+ }
}
updateLocusId();
}
@@ -1220,18 +1257,32 @@
TaskView ignoreResetTaskView =
mIgnoreResetTaskId == -1 ? null : getTaskViewByTaskId(mIgnoreResetTaskId);
- final int requiredTaskCount = tasks.size();
- if (getTaskViewCount() != requiredTaskCount) {
+ int[] splitTaskIds =
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds();
+ int requiredGroupTaskViews = splitTaskIds.length / 2;
+
+ // Subtract half the number of split tasks and not total number because we've already
+ // added a GroupedTaskView when swipe up gesture happens.
+ // This will need to change if we start showing GroupedTaskViews during swipe up from home
+ int requiredTaskViewCount = tasks.size() - requiredGroupTaskViews;
+
+ if (getTaskViewCount() != requiredTaskViewCount) {
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
}
- for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
- addView(getTaskViewFromPool());
+
+ for (int i = getTaskViewCount(); i < requiredTaskViewCount; i++) {
+ addView(getTaskViewFromPool(false));
}
- while (getTaskViewCount() > requiredTaskCount) {
+ while (getTaskViewCount() > requiredTaskViewCount) {
removeView(getChildAt(getChildCount() - 1));
}
- if (requiredTaskCount > 0) {
+ while (requiredGroupTaskViews > 0) {
+ // Add to front of list
+ addView(getTaskViewFromPool(true), 0);
+ requiredGroupTaskViews--;
+ }
+ if (requiredTaskViewCount > 0) {
addView(mClearAllButton);
}
}
@@ -1245,12 +1296,28 @@
+ " runningTaskViewId: " + mRunningTaskViewId
+ " forTaskView: " + getTaskViewFromTaskViewId(mRunningTaskViewId));
- // Rebind and reset all task views
- for (int i = requiredTaskCount - 1; i >= 0; i--) {
- final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
- final Task task = tasks.get(i);
+ for (int taskViewIndex = requiredTaskViewCount - 1, taskDataIndex = tasks.size() - 1;
+ taskViewIndex >= 0;
+ taskViewIndex--, taskDataIndex--) {
+ final int pageIndex = requiredTaskViewCount - taskViewIndex - 1 + mTaskViewStartIndex;
+ final Task task = tasks.get(taskDataIndex);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
- taskView.bind(task, mOrientationState);
+ if (taskView instanceof GroupedTaskView) {
+ Task leftTop;
+ Task rightBottom;
+ if (task.key.id == splitTaskIds[0]) {
+ leftTop = task;
+ taskDataIndex--;
+ rightBottom = tasks.get(taskDataIndex);
+ } else {
+ rightBottom = task;
+ taskDataIndex--;
+ leftTop = tasks.get(taskDataIndex);
+ }
+ ((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState);
+ } else {
+ taskView.bind(task, mOrientationState);
+ }
}
// Keep same previous focused task
@@ -1262,6 +1329,7 @@
mFocusedTaskViewId = newFocusedTaskView != null ?
newFocusedTaskView.getTaskViewId() : -1;
updateTaskSize();
+ updateChildTaskOrientations();
TaskView newRunningTaskView = null;
if (runningTaskId != -1) {
@@ -1270,8 +1338,8 @@
newRunningTaskView = getTaskViewByTaskId(runningTaskId);
if (newRunningTaskView == null) {
StringBuilder sb = new StringBuilder();
- for (int i = requiredTaskCount - 1; i >= 0; i--) {
- final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
+ for (int i = requiredTaskViewCount - 1; i >= 0; i--) {
+ final int pageIndex = requiredTaskViewCount - i - 1 + mTaskViewStartIndex;
final TaskView taskView = (TaskView) getChildAt(pageIndex);
int taskViewId = taskView.getTaskViewId();
sb.append(" taskViewId: " + taskViewId
@@ -1355,12 +1423,12 @@
// Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
// to reset the params after it settles in Overview from swipe up so that we don't
// render with obsolete param values.
- mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 0;
- mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
- mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
- mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-
- mLiveTileParams.setTargetAlpha(1);
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ remoteTargetHandle.mTaskViewSimulator.taskPrimaryTranslation.value = 0;
+ remoteTargetHandle.mTaskViewSimulator.taskSecondaryTranslation.value = 0;
+ remoteTargetHandle.mTaskViewSimulator.fullScreenProgress.value = 0;
+ remoteTargetHandle.mTaskViewSimulator.recentsViewScale.value = 1;
+ });
// Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
// null.
@@ -1414,7 +1482,8 @@
setPageSpacing(dp.overviewPageSpacing);
// Propagate DeviceProfile change event.
- mLiveTileTaskViewSimulator.setDp(dp);
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator.setDp(dp));
mActionsView.setDp(dp);
mOrientationState.setDeviceProfile(dp);
@@ -1525,8 +1594,7 @@
}
public void getTaskSize(Rect outRect) {
- mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
- mOrientationHandler);
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
mLastComputedTaskSize.set(outRect);
}
@@ -1534,8 +1602,7 @@
* Returns the size of task selected to enter modal state.
*/
public Point getSelectedTaskSize() {
- mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect,
- mOrientationHandler);
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect);
return new Point(mTempRect.width(), mTempRect.height());
}
@@ -1668,8 +1735,17 @@
visible = lower <= index && index <= upper;
}
if (visible) {
- if (task == mTmpRunningTask) {
- // Skip loading if this is the task that we are animating into
+ boolean skipLoadingTask = false;
+ if (mTmpRunningTasks != null) {
+ for (Task t : mTmpRunningTasks) {
+ if (task == t) {
+ // Skip loading if this is the task that we are animating into
+ skipLoadingTask = true;
+ break;
+ }
+ }
+ }
+ if (skipLoadingTask) {
continue;
}
if (!mHasVisibleTaskData.get(task.key.id)) {
@@ -1744,7 +1820,8 @@
}
}
setEnableDrawingLiveTile(false);
- mLiveTileParams.setTargetSet(null);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+ .setTargetSet(null));
// These are relatively expensive and don't need to be done this frame (RecentsView isn't
// visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -1811,8 +1888,10 @@
* Handle the edge case where Recents could increment task count very high over long
* period of device usage. Probably will never happen, but meh.
*/
- private TaskView getTaskViewFromPool() {
- TaskView taskView = mTaskViewPool.getView();
+ private <T extends TaskView> T getTaskViewFromPool(boolean isGrouped) {
+ T taskView = isGrouped ?
+ (T) mGroupedTaskViewPool.getView() :
+ (T) mTaskViewPool.getView();
taskView.setTaskViewId(mTaskViewIdCount);
if (mTaskViewIdCount == Integer.MAX_VALUE) {
mTaskViewIdCount = 0;
@@ -1848,7 +1927,7 @@
/**
* Called when a gesture from an app is starting.
*/
- public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
+ public void onGestureAnimationStart(RunningTaskInfo[] runningTaskInfo) {
mGestureActive = true;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
@@ -1899,7 +1978,6 @@
return as;
}
-
private void updateChildTaskOrientations() {
for (int i = 0; i < getTaskViewCount(); i++) {
getTaskViewAt(i).setOrientationState(mOrientationState);
@@ -1915,7 +1993,7 @@
*/
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
- TaskViewSimulator taskViewSimulator) {
+ TaskViewSimulator[] taskViewSimulators) {
mCurrentGestureEndTarget = endTarget;
if (endTarget == GestureState.GestureEndTarget.RECENTS) {
setEnableFreeScroll(true);
@@ -1932,13 +2010,16 @@
runningTaskView.getGridTranslationX(),
runningTaskView.getGridTranslationY());
}
- if (animatorSet == null) {
- setGridProgress(1);
- taskViewSimulator.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation;
- } else {
- animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
- animatorSet.play(taskViewSimulator.taskPrimaryTranslation.animateToValue(
- runningTaskPrimaryGridTranslation));
+ for (TaskViewSimulator tvs : taskViewSimulators) {
+ if (animatorSet == null) {
+ setGridProgress(1);
+ tvs.taskPrimaryTranslation.value =
+ runningTaskPrimaryGridTranslation;
+ } else {
+ animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
+ animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
+ runningTaskPrimaryGridTranslation));
+ }
}
}
}
@@ -1967,7 +2048,21 @@
/**
* Returns true if we should add a stub taskView for the running task id
*/
- protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
+ protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
+ if (runningTaskInfos.length > 1) {
+ // * Always create new view for GroupedTaskView
+ // * Remove existing associated taskViews for tasks currently in split
+ for (RunningTaskInfo rti : runningTaskInfos) {
+ TaskView taskView = getTaskViewByTaskId(rti.taskId);
+ if (taskView == null) {
+ continue;
+ }
+ taskView.onTaskListVisibilityChanged(false);
+ removeView(taskView);
+ }
+ return true;
+ }
+ RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.taskId) == null;
}
@@ -1977,29 +2072,44 @@
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
* is called. Also scrolls the view to this task.
*/
- public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
+ public void showCurrentTask(RunningTaskInfo[] runningTaskInfo) {
int runningTaskViewId = -1;
+ boolean needGroupTaskView = runningTaskInfo.length > 1;
+ RunningTaskInfo taskInfo = runningTaskInfo[0];
if (shouldAddStubTaskView(runningTaskInfo)) {
boolean wasEmpty = getChildCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
- final TaskView taskView = getTaskViewFromPool();
+ final TaskView taskView;
+ if (needGroupTaskView) {
+ taskView = getTaskViewFromPool(true);
+ RunningTaskInfo secondaryTaskInfo = runningTaskInfo[1];
+ mTmpRunningTasks = new Task[]{
+ Task.from(new TaskKey(taskInfo), taskInfo, false),
+ Task.from(new TaskKey(secondaryTaskInfo), secondaryTaskInfo, false)
+ };
+ addView(taskView, mTaskViewStartIndex);
+ ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
+ mOrientationState);
+ } else {
+ taskView = getTaskViewFromPool(false);
+ addView(taskView, mTaskViewStartIndex);
+ // The temporary running task is only used for the duration between the start of the
+ // gesture and the task list is loaded and applied
+ mTmpRunningTasks = new Task[]{Task.from(new TaskKey(taskInfo), taskInfo, false)};
+ taskView.bind(mTmpRunningTasks[0], mOrientationState);
+ }
runningTaskViewId = taskView.getTaskViewId();
- addView(taskView, mTaskViewStartIndex);
if (wasEmpty) {
addView(mClearAllButton);
}
- // The temporary running task is only used for the duration between the start of the
- // gesture and the task list is loaded and applied
- mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
- taskView.bind(mTmpRunningTask, mOrientationState);
// Measure and layout immediately so that the scroll values is updated instantly
// as the user might be quick-switching
measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
makeMeasureSpec(getMeasuredHeight(), EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
- } else if (getTaskViewByTaskId(runningTaskInfo.taskId) != null) {
- runningTaskViewId = getTaskViewByTaskId(runningTaskInfo.taskId).getTaskViewId();
+ } else if (!needGroupTaskView && getTaskViewByTaskId(taskInfo.taskId) != null) {
+ runningTaskViewId = getTaskViewByTaskId(taskInfo.taskId).getTaskViewId();
}
boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -2010,6 +2120,7 @@
setRunningTaskHidden(runningTaskTileHidden);
// Update task size after setting current task.
updateTaskSize();
+ updateChildTaskOrientations();
// Reload the task list
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
@@ -2416,8 +2527,11 @@
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// alpha is set to 0 so that it can be recycled in the view pool properly
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
- anim.setFloat(mLiveTileParams, TransformParams.TARGET_ALPHA, 0,
- clampToProgress(ACCEL, 0, 0.5f));
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ TransformParams params = remoteTargetHandle.mTransformParams;
+ anim.setFloat(params, TransformParams.TARGET_ALPHA, 0,
+ clampToProgress(ACCEL, 0, 0.5f));
+ });
}
anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
@@ -2436,10 +2550,12 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
&& taskView.isRunningTask()) {
anim.addOnFrameCallback(() -> {
- mLiveTileTaskViewSimulator.taskSecondaryTranslation.value =
- mOrientationHandler.getSecondaryValue(
- taskView.getTranslationX(),
- taskView.getTranslationY());
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+ .taskSecondaryTranslation.value = mOrientationHandler
+ .getSecondaryValue(taskView.getTranslationX(),
+ taskView.getTranslationY()
+ ));
redrawLiveTile();
});
}
@@ -2490,7 +2606,6 @@
boolean showAsGrid = showAsGrid();
int taskCount = getTaskViewCount();
int dismissedIndex = indexOfChild(dismissedTaskView);
- int dismissedTaskId = dismissedTaskView.getTaskIds()[0];
int dismissedTaskViewId = dismissedTaskView.getTaskViewId();
// Grid specific properties.
@@ -2588,9 +2703,14 @@
&& child instanceof TaskView
&& ((TaskView) child).isRunningTask()) {
anim.addOnFrameCallback(() -> {
- mLiveTileTaskViewSimulator.taskPrimaryTranslation.value =
- mOrientationHandler.getPrimaryValue(child.getTranslationX(),
- child.getTranslationY());
+ runActionOnRemoteHandles(
+ remoteTargetHandle ->
+ remoteTargetHandle.mTaskViewSimulator
+ .taskPrimaryTranslation.value =
+ mOrientationHandler.getPrimaryValue(
+ child.getTranslationX(),
+ child.getTranslationY()
+ ));
redrawLiveTile();
});
}
@@ -2672,9 +2792,9 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get()
&& dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> removeTaskInternal(dismissedTaskId));
+ () -> removeTaskInternal(dismissedTaskViewId));
} else {
- removeTaskInternal(dismissedTaskId);
+ removeTaskInternal(dismissedTaskViewId);
}
mActivity.getStatsLogManager().logger()
.withItemInfo(dismissedTaskView.getItemInfo())
@@ -2710,6 +2830,7 @@
finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
}
updateTaskSize(/*isTaskDismissal=*/ true);
+ updateChildTaskOrientations();
// Update scroll and snap to page.
updateScrollSynchronously();
@@ -2804,9 +2925,17 @@
return lastVisibleIndex;
}
- private void removeTaskInternal(int dismissedTaskId) {
+ private void removeTaskInternal(int dismissedTaskViewId) {
+ int[] taskIds = getTaskIdsForTaskViewId(dismissedTaskViewId);
+ int primaryTaskId = taskIds[0];
+ int secondaryTaskId = taskIds[1];
UI_HELPER_EXECUTOR.getHandler().postDelayed(
- () -> ActivityManagerWrapper.getInstance().removeTask(dismissedTaskId),
+ () -> {
+ ActivityManagerWrapper.getInstance().removeTask(primaryTaskId);
+ if (secondaryTaskId != -1) {
+ ActivityManagerWrapper.getInstance().removeTask(secondaryTaskId);
+ }
+ },
REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
}
@@ -3139,7 +3268,9 @@
mLastComputedTaskStartPushOutDistance = null;
mLastComputedTaskEndPushOutDistance = null;
updatePageOffsets();
- mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+ .setScroll(getScrollOffset()));
setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
@@ -3203,7 +3334,9 @@
translationProperty.set(child, totalTranslation);
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
&& i == getRunningTaskIndex()) {
- mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = totalTranslation;
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+ .taskPrimaryTranslation.value = totalTranslation);
redrawLiveTile();
}
}
@@ -3306,7 +3439,9 @@
TaskView task = getTaskViewAt(i);
task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY());
}
- mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+ .recentsViewSecondaryTranslation.value = translation);
}
protected void setTaskViewsPrimarySplitTranslation(float translation) {
@@ -3508,8 +3643,6 @@
resetTaskVisuals();
mSplitHiddenTaskView.setVisibility(VISIBLE);
mSplitHiddenTaskView = null;
- mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
- mSecondSplitHiddenTaskView = null;
mSplitHiddenTaskViewIndex = -1;
if (mFirstFloatingTaskView != null) {
mActivity.getRootView().removeView(mFirstFloatingTaskView);
@@ -3518,6 +3651,8 @@
if (mSecondFloatingTaskView != null) {
mActivity.getRootView().removeView(mSecondFloatingTaskView);
mSecondFloatingTaskView = null;
+ mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
+ mSecondSplitHiddenTaskView = null;
}
}
@@ -3613,10 +3748,12 @@
int runningTaskIndex = recentsView.getRunningTaskIndex();
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && runningTaskIndex != -1
&& runningTaskIndex != taskIndex) {
- anim.play(ObjectAnimator.ofFloat(
- recentsView.getLiveTileTaskViewSimulator().taskPrimaryTranslation,
- AnimatedFloat.VALUE,
- primaryTranslation));
+ for (RemoteTargetHandle remoteHandle : recentsView.getRemoteTargetHandles()) {
+ anim.play(ObjectAnimator.ofFloat(
+ remoteHandle.mTaskViewSimulator.taskPrimaryTranslation,
+ AnimatedFloat.VALUE,
+ primaryTranslation));
+ }
}
int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
@@ -3696,7 +3833,9 @@
mPendingAnimation = new PendingAnimation(duration);
mPendingAnimation.add(anim);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+ .addOverviewToAppAnim(mPendingAnimation, interpolator));
mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
}
mPendingAnimation.addEndListener(isSuccess -> {
@@ -3787,31 +3926,87 @@
}
public void redrawLiveTile() {
- if (mLiveTileParams.getTargetSet() != null) {
- mLiveTileTaskViewSimulator.apply(mLiveTileParams);
- }
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ TransformParams params = remoteTargetHandle.mTransformParams;
+ if (params.getTargetSet() != null) {
+ remoteTargetHandle.mTaskViewSimulator.apply(params);
+ }
+ });
}
- public TaskViewSimulator getLiveTileTaskViewSimulator() {
- return mLiveTileTaskViewSimulator;
- }
-
- public TransformParams getLiveTileParams() {
- return mLiveTileParams;
+ public RemoteTargetHandle[] getRemoteTargetHandles() {
+ return mRemoteTargetHandles;
}
// TODO: To be removed in a follow up CL
public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
RecentsAnimationTargets recentsAnimationTargets) {
mRecentsAnimationController = recentsAnimationController;
- if (recentsAnimationTargets != null && recentsAnimationTargets.apps.length > 0) {
- if (mSyncTransactionApplier != null) {
- recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier);
- }
- mLiveTileTaskViewSimulator.setPreview(
- recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]);
- mLiveTileParams.setTargetSet(recentsAnimationTargets);
+ if (recentsAnimationTargets == null || recentsAnimationTargets.apps.length == 0) {
+ return;
}
+
+ if (mSyncTransactionApplier != null) {
+ recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier);
+ }
+
+ // TODO Consolidate this shared code with SwipeUpAnimationLogic (or mabe just reuse
+ // what that class has and pass it into here
+ mRemoteTargetHandles = new RemoteTargetHandle[recentsAnimationTargets.apps.length];
+ TaskViewSimulator primaryTvs = createTaskViewSimulator();
+ mRemoteTargetHandles[0] = new RemoteTargetHandle(primaryTvs, new TransformParams());
+ if (recentsAnimationTargets.apps.length == 1) {
+ mRemoteTargetHandles[0].mTaskViewSimulator
+ .setPreview(recentsAnimationTargets.apps[0], null);
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(recentsAnimationTargets);
+ } else {
+ TaskViewSimulator secondaryTvs = createTaskViewSimulator();
+ secondaryTvs.setOrientationState(mOrientationState);
+ secondaryTvs.recentsViewScale.value = 1;
+
+ mRemoteTargetHandles[1] = new RemoteTargetHandle(secondaryTvs, new TransformParams());
+ RemoteAnimationTargetCompat dividerTarget =
+ recentsAnimationTargets.getNonAppTargetOfType(TYPE_DOCK_DIVIDER);
+ RemoteAnimationTargetCompat primaryTaskTarget = recentsAnimationTargets.apps[0];
+ RemoteAnimationTargetCompat secondaryTaskTarget = recentsAnimationTargets.apps[1];
+ SplitConfigurationOptions.StagedSplitBounds
+ info = new SplitConfigurationOptions.StagedSplitBounds(
+ primaryTaskTarget.screenSpaceBounds,
+ secondaryTaskTarget.screenSpaceBounds, dividerTarget.screenSpaceBounds);
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, info);
+ mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget, info);
+ RemoteAnimationTargets rats = new RemoteAnimationTargets(
+ new RemoteAnimationTargetCompat[]{primaryTaskTarget},
+ recentsAnimationTargets.wallpapers, recentsAnimationTargets.nonApps,
+ MODE_CLOSING
+ );
+ RemoteAnimationTargets splitRats = new RemoteAnimationTargets(
+ new RemoteAnimationTargetCompat[]{secondaryTaskTarget},
+ recentsAnimationTargets.wallpapers, recentsAnimationTargets.nonApps,
+ MODE_CLOSING
+ );
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(rats);
+ mRemoteTargetHandles[1].mTransformParams.setTargetSet(splitRats);
+ }
+ }
+
+ /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
+ private void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
+ if (mRemoteTargetHandles == null) {
+ return;
+ }
+
+ for (RemoteTargetHandle handle : mRemoteTargetHandles) {
+ consumer.accept(handle);
+ }
+ }
+
+ private TaskViewSimulator createTaskViewSimulator() {
+ TaskViewSimulator tvs = new TaskViewSimulator(getContext(), getSizeStrategy());
+ tvs.setOrientationState(mOrientationState);
+ tvs.setDp(mActivity.getDeviceProfile());
+ tvs.recentsViewScale.value = 1;
+ return tvs;
}
public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
@@ -4117,14 +4312,42 @@
}
return;
}
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
- switchToScreenshot(mRunningTaskViewId == -1 ? null
- : mRecentsAnimationController.screenshotTask(runningTaskId), onFinishRunnable);
+
+ switchToScreenshotInternal(onFinishRunnable);
+ }
+
+ private void switchToScreenshotInternal(Runnable onFinishRunnable) {
+ TaskView taskView = getRunningTaskView();
+ if (taskView == null) {
+ onFinishRunnable.run();
+ return;
+ }
+
+ taskView.setShowScreenshot(true);
+ for (TaskView.TaskIdAttributeContainer container :
+ taskView.getTaskIdAttributeContainers()) {
+ if (container == null) {
+ continue;
+ }
+
+ ThumbnailData td =
+ mRecentsAnimationController.screenshotTask(container.getTask().key.id);
+ TaskThumbnailView thumbnailView = container.getThumbnailView();
+ if (td != null) {
+ thumbnailView.setThumbnail(container.getTask(), td);
+ } else {
+ thumbnailView.refresh();
+ }
+ }
+ ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
}
/**
* Switch the current running task view to static snapshot mode, using the
* provided thumbnail data as the snapshot.
+ * TODO(b/195609063) Consolidate this method w/ the one above, except this thumbnail data comes
+ * from gesture state, which is a larger change of it having to keep track of multiple tasks.
+ * OR. Maybe it doesn't need to pass in a thumbnail and we can use the exact same flow as above
*/
public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
TaskView taskView = getRunningTaskView();
@@ -4277,7 +4500,8 @@
}
private void dispatchScrollChanged() {
- mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.mTaskViewSimulator.setScroll(getScrollOffset()));
for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
mScrollListeners.get(i).onScrollChanged();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 35e21ad..0577cce 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -211,7 +211,7 @@
return Insets.NONE;
}
- if (!TaskView.CLIP_STATUS_AND_NAV_BARS) {
+ if (!TaskView.clipStatusAndNavBars(mActivity.getDeviceProfile())) {
return Insets.NONE;
}
@@ -440,7 +440,7 @@
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
- RectF thumbnailClipHint = TaskView.CLIP_STATUS_AND_NAV_BARS
+ RectF thumbnailClipHint = TaskView.clipStatusAndNavBars(dp)
? new RectF(thumbnailData.insets) : new RectF();
float scale = thumbnailData.scale;
@@ -554,7 +554,7 @@
-thumbnailClipHint.left * scale,
-thumbnailClipHint.top * scale);
} else {
- setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds);
+ setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds, dp);
}
final float widthWithInsets;
@@ -599,7 +599,7 @@
}
private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale,
- Rect thumbnailPosition) {
+ Rect thumbnailPosition, DeviceProfile dp) {
float newLeftInset = 0;
float newTopInset = 0;
float translateX = 0;
@@ -626,7 +626,7 @@
}
mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
mMatrix.postTranslate(translateX, translateY);
- if (TaskView.FULL_THUMBNAIL) {
+ if (TaskView.useFullThumbnail(dp)) {
mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
}
}
@@ -634,8 +634,8 @@
/**
* Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
*/
- public RectF getInsetsToDrawInFullscreen() {
- return TaskView.FULL_THUMBNAIL ? mClippedInsets : EMPTY_RECT_F;
+ public RectF getInsetsToDrawInFullscreen(DeviceProfile dp) {
+ return TaskView.useFullThumbnail(dp) ? mClippedInsets : EMPTY_RECT_F;
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2c33b6d..174e1b3 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -91,6 +91,7 @@
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.SwipeUpAnimationLogic.RemoteTargetHandle;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
@@ -100,16 +101,20 @@
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.lang.annotation.Retention;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Stream;
/**
* A task in the Recents view.
@@ -144,12 +149,16 @@
* Should the TaskView display clip off the status and navigation bars in recents. When this
* is false the overview shows the whole screen scaled down instead.
*/
- public static final boolean CLIP_STATUS_AND_NAV_BARS = false;
+ public static boolean clipStatusAndNavBars(DeviceProfile deviceProfile) {
+ return deviceProfile.isTaskbarPresentInApps;
+ }
/**
* Should the TaskView scale down to fit whole thumbnail in fullscreen.
*/
- public static final boolean FULL_THUMBNAIL = false;
+ public static boolean useFullThumbnail(DeviceProfile deviceProfile) {
+ return deviceProfile.isTaskbarPresentInApps;
+ };
private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
@@ -329,8 +338,8 @@
private final TaskOutlineProvider mOutlineProvider;
- private Task mTask;
- private TaskThumbnailView mSnapshotView;
+ protected Task mTask;
+ protected TaskThumbnailView mSnapshotView;
private IconView mIconView;
private final DigitalWellBeingToast mDigitalWellBeingToast;
private float mFullscreenProgress;
@@ -338,7 +347,7 @@
private float mNonGridScale = 1;
private float mDismissScale = 1;
private final FullscreenDrawParams mCurrentFullscreenParams;
- private final StatefulActivity mActivity;
+ protected final StatefulActivity mActivity;
// Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
private float mDismissTranslationX;
@@ -367,7 +376,12 @@
private float mStableAlpha = 1;
private int mTaskViewId = -1;
- private final int[] mTaskIdContainer = new int[]{-1, -1};
+ /**
+ * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
+ */
+ protected final int[] mTaskIdContainer = new int[]{-1, -1};
+ protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer =
+ new TaskIdAttributeContainer[2];
private boolean mShowScreenshot;
@@ -518,10 +532,15 @@
cancelPendingLoadTasks();
mTask = task;
mTaskIdContainer[0] = mTask.key.id;
+ mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView);
mSnapshotView.bind(task);
setOrientationState(orientedState);
}
+ public TaskIdAttributeContainer[] getTaskIdAttributeContainers() {
+ return mTaskIdAttributeContainer;
+ }
+
public Task getTask() {
return mTask;
}
@@ -563,7 +582,29 @@
mIsClickableAsLiveTile = false;
RecentsView recentsView = getRecentsView();
- final RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
+ RemoteAnimationTargets targets;
+ RemoteTargetHandle[] remoteTargetHandles =
+ recentsView.mRemoteTargetHandles;
+ if (remoteTargetHandles.length == 1) {
+ targets = remoteTargetHandles[0].mTransformParams.getTargetSet();
+ } else {
+ TransformParams topLeftParams = remoteTargetHandles[0].mTransformParams;
+ TransformParams rightBottomParams = remoteTargetHandles[1].mTransformParams;
+ RemoteAnimationTargetCompat[] apps = Stream.concat(
+ Arrays.stream(topLeftParams.getTargetSet().apps),
+ Arrays.stream(rightBottomParams.getTargetSet().apps))
+ .toArray(RemoteAnimationTargetCompat[]::new);
+ RemoteAnimationTargetCompat[] wallpapers = Stream.concat(
+ Arrays.stream(topLeftParams.getTargetSet().wallpapers),
+ Arrays.stream(rightBottomParams.getTargetSet().wallpapers))
+ .toArray(RemoteAnimationTargetCompat[]::new);
+ RemoteAnimationTargetCompat[] nonApps = Stream.concat(
+ Arrays.stream(topLeftParams.getTargetSet().nonApps),
+ Arrays.stream(rightBottomParams.getTargetSet().nonApps))
+ .toArray(RemoteAnimationTargetCompat[]::new);
+ targets = new RemoteAnimationTargets(apps, wallpapers, nonApps,
+ topLeftParams.getTargetSet().targetMode);
+ }
if (targets == null) {
// 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.
@@ -723,11 +764,11 @@
}
}
- private boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
+ protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
return (dataChange & flag) == flag;
}
- private void cancelPendingLoadTasks() {
+ protected void cancelPendingLoadTasks() {
if (mThumbnailLoadRequest != null) {
mThumbnailLoadRequest.cancel();
mThumbnailLoadRequest = null;
@@ -784,8 +825,11 @@
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int taskIconMargin = deviceProfile.overviewTaskMarginPx;
+ boolean isGridTask = deviceProfile.overviewShowAsGrid && !isFocusedTask();
int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
+ int taskMargin = isGridTask ? deviceProfile.overviewTaskMarginGridPx
+ : deviceProfile.overviewTaskMarginPx;
+ int taskIconMargin = snapshotParams.topMargin - taskIconHeight - taskMargin;
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
switch (orientationHandler.getRotation()) {
case ROTATION_90:
@@ -817,6 +861,9 @@
iconParams.width = iconParams.height = taskIconHeight;
mIconView.setLayoutParams(iconParams);
mIconView.setRotation(orientationHandler.getDegreesRotated());
+ int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
+ : deviceProfile.overviewTaskIconDrawableSizePx;
+ mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
mSnapshotView.setLayoutParams(snapshotParams);
mSnapshotView.getTaskOverlay().updateOrientationState(orientationState);
@@ -1360,7 +1407,6 @@
float boxTranslationY;
int expectedWidth;
int expectedHeight;
- int iconDrawableSize;
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
if (deviceProfile.overviewShowAsGrid) {
final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx;
@@ -1376,13 +1422,11 @@
// that is associated with the original orientation of the focused task.
boxWidth = taskWidth;
boxHeight = taskHeight;
- iconDrawableSize = deviceProfile.overviewTaskIconDrawableSizePx;
} else {
// Otherwise task is in grid, and should use lastComputedGridTaskSize.
Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
boxWidth = lastComputedGridTaskSize.width();
boxHeight = lastComputedGridTaskSize.height();
- iconDrawableSize = deviceProfile.overviewTaskIconDrawableSizeGridPx;
}
// Bound width/height to the box size.
@@ -1399,7 +1443,6 @@
boxTranslationY = 0f;
expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT;
expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT;
- iconDrawableSize = deviceProfile.overviewTaskIconDrawableSizePx;
}
setNonGridScale(nonGridScale);
@@ -1409,7 +1452,6 @@
params.height = expectedHeight;
setLayoutParams(params);
}
- mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
}
private float getGridTrans(float endTranslation) {
@@ -1489,12 +1531,16 @@
*/
public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
- RectF insets = pph.getInsetsToDrawInFullscreen();
+ RectF insets = pph.getInsetsToDrawInFullscreen(dp);
float currentInsetsLeft = insets.left * fullscreenProgress;
float currentInsetsRight = insets.right * fullscreenProgress;
+ float insetsBottom = insets.bottom;
+ if (dp.isTaskbarPresentInApps) {
+ insetsBottom = Math.max(0, insetsBottom - dp.taskbarSize);
+ }
mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress,
- currentInsetsRight, insets.bottom * fullscreenProgress);
+ currentInsetsRight, insetsBottom * fullscreenProgress);
float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius;
mCurrentDrawnCornerRadius =
@@ -1509,4 +1555,22 @@
}
}
+
+ public class TaskIdAttributeContainer {
+ private final TaskThumbnailView thumbnailView;
+ private final Task task;
+
+ public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView) {
+ this.task = task;
+ this.thumbnailView = thumbnailView;
+ }
+
+ public TaskThumbnailView getThumbnailView() {
+ return thumbnailView;
+ }
+
+ public Task getTask() {
+ return task;
+ }
+ }
}
diff --git a/res/layout/launcher_preview_two_panel_layout.xml b/res/layout/launcher_preview_two_panel_layout.xml
index 7b227e0..f76fc5a 100644
--- a/res/layout/launcher_preview_two_panel_layout.xml
+++ b/res/layout/launcher_preview_two_panel_layout.xml
@@ -25,24 +25,24 @@
android:layout_height="match_parent">
<com.android.launcher3.CellLayout
- android:id="@+id/workspace_left"
+ android:id="@+id/workspace"
android:layout_width="0dp"
android:layout_height="0dp"
android:theme="@style/HomeScreenElementTheme"
launcher:containerType="workspace"
launcher:layout_constraintStart_toStartOf="parent"
launcher:layout_constraintTop_toTopOf="parent"
- launcher:layout_constraintEnd_toStartOf="@id/workspace"
+ launcher:layout_constraintEnd_toStartOf="@id/workspace_right"
launcher:layout_constraintBottom_toBottomOf="parent"
launcher:pageIndicator="@+id/page_indicator" />
<com.android.launcher3.CellLayout
- android:id="@+id/workspace"
+ android:id="@+id/workspace_right"
android:layout_width="0dp"
android:layout_height="0dp"
android:theme="@style/HomeScreenElementTheme"
launcher:containerType="workspace"
- launcher:layout_constraintStart_toEndOf="@id/workspace_left"
+ launcher:layout_constraintStart_toEndOf="@id/workspace"
launcher:layout_constraintTop_toTopOf="parent"
launcher:layout_constraintEnd_toEndOf="parent"
launcher:layout_constraintBottom_toBottomOf="parent"
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index c4ee9ae..25a1739 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -101,8 +101,8 @@
<string name="folder_tap_to_rename" msgid="4017685068016979677">"மாற்றிய பெயரைச் சேமிக்க, தட்டவும்"</string>
<string name="folder_closed" msgid="4100806530910930934">"கோப்புறை மூடப்பட்டது"</string>
<string name="folder_renamed" msgid="1794088362165669656">"கோப்புறை <xliff:g id="NAME">%1$s</xliff:g> என மறுபெயரிடப்பட்டது"</string>
- <string name="folder_name_format_exact" msgid="8626242716117004803">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> கோப்புகள்"</string>
- <string name="folder_name_format_overflow" msgid="4270108890534995199">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> அல்லது அதற்கு அதிகமான கோப்புகள்"</string>
+ <string name="folder_name_format_exact" msgid="8626242716117004803">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ஃபைல்கள்"</string>
+ <string name="folder_name_format_overflow" msgid="4270108890534995199">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> அல்லது அதற்கு அதிகமான ஃபைல்கள்"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"வால்பேப்பர்கள்"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"வால்பேப்பர் & ஸ்டைல்"</string>
<string name="settings_button_text" msgid="8873672322605444408">"முகப்பு அமைப்புகள்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 6893888..22aa785 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -58,7 +58,7 @@
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"విడ్జెట్ సెట్టింగ్లను మార్చడానికి ట్యాప్ చేయండి"</string>
<string name="widget_education_close_button" msgid="8676165703104836580">"అర్థమైంది"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"విడ్జెట్ సెట్టింగ్లను మార్చండి"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"అప్లికేషన్లను శోధించండి"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"అప్లికేషన్లను వెతకండి"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"అప్లికేషన్లను లోడ్ చేస్తోంది…"</string>
<string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అప్లికేషన్లేవీ కనుగొనబడలేదు"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"మరిన్ని యాప్ల కోసం వెతుకు"</string>
@@ -68,9 +68,9 @@
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"షార్ట్కట్ను తరలించడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కండి & హోల్డ్ చేయండి."</string>
<string name="out_of_space" msgid="6692471482459245734">"ఈ మొదటి స్క్రీన్లో స్థలం లేదు"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"యాప్ల జాబితా"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"వ్యక్తిగత యాప్ల జాబితా"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"కార్యాలయ యాప్ల జాబితా"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"యాప్ల లిస్ట్"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"వ్యక్తిగత యాప్ల లిస్ట్"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"కార్యాలయ యాప్ల లిస్ట్"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"తీసివేయి"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్స్టాల్ చేయి"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
@@ -78,11 +78,11 @@
<string name="dismiss_prediction_label" msgid="3357562989568808658">"యాప్ను సూచించవద్దు"</string>
<string name="pin_prediction" msgid="4196423321649756498">"సూచనను పిన్ చేయండి"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"షార్ట్కట్లను ఇన్స్టాల్ చేయడం"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా సత్వరమార్గాలను జోడించడానికి యాప్ను అనుమతిస్తుంది."</string>
- <string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడం"</string>
- <string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్లో సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
- <string name="permlab_write_settings" msgid="3574213698004620587">"హోమ్ సెట్టింగ్లు మరియు సత్వరమార్గాలను వ్రాయడం"</string>
- <string name="permdesc_write_settings" msgid="5440712911516509985">"హోమ్లో సెట్టింగ్లు మరియు సత్వరమార్గాలను మార్చడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా షార్ట్కట్లను జోడించడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్లు మరియు షార్ట్కట్లను చదవడం"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్లో సెట్టింగ్లు మరియు షార్ట్కట్లను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"హోమ్ సెట్టింగ్లు మరియు షార్ట్కట్లను వ్రాయడం"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"హోమ్లో సెట్టింగ్లు మరియు షార్ట్కట్లను మార్చడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="msg_no_phone_permission" msgid="9208659281529857371">"ఫోన్ కాల్స్ను చేసేందుకు <xliff:g id="APP_NAME">%1$s</xliff:g>కి అనుమతి లేదు"</string>
<string name="gadget_error_text" msgid="740356548025791839">"విడ్జెట్ను లోడ్ చేయడం సాధ్యం కాలేదు"</string>
<string name="gadget_setup_text" msgid="1745356155479272374">"సెటప్ను పూర్తి చేయడానికి ట్యాప్ చేయండి"</string>
@@ -126,8 +126,8 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g>ను ఇన్స్టాల్ చేయడం, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> డౌన్లోడ్ అవుతోంది, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ఇన్స్టాల్ కావడానికి వేచి ఉంది"</string>
- <string name="widgets_list" msgid="796804551140113767">"విడ్జెట్ల జాబితా"</string>
- <string name="widgets_list_closed" msgid="6141506579418771922">"విడ్జెట్ల జాబితా మూసివేయబడింది"</string>
+ <string name="widgets_list" msgid="796804551140113767">"విడ్జెట్ల లిస్ట్"</string>
+ <string name="widgets_list_closed" msgid="6141506579418771922">"విడ్జెట్ల లిస్ట్ మూసివేయబడింది"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"హోమ్ స్క్రీన్కు జోడించండి"</string>
<string name="action_move_here" msgid="2170188780612570250">"అంశాన్ని ఇక్కడికి తరలించు"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"అంశం హోమ్స్క్రీన్కి జోడించబడింది"</string>
@@ -141,7 +141,7 @@
<string name="add_to_folder" msgid="9040534766770853243">"ఈ ఫోల్డర్కి జోడించండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> గల ఫోల్డర్కు జోడించు"</string>
<string name="added_to_folder" msgid="4793259502305558003">"అంశం ఫోల్డర్కు జోడించబడింది"</string>
- <string name="create_folder_with" msgid="4050141361160214248">"ఈ పేరుతో ఫోల్డర్ను సృష్టించండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="create_folder_with" msgid="4050141361160214248">"ఈ పేరుతో ఫోల్డర్ను క్రియేట్ చేయండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_created" msgid="6409794597405184510">"ఫోల్డర్ సృష్టించబడింది"</string>
<string name="action_move_to_workspace" msgid="1603837886334246317">"హోమ్స్క్రీన్కు తరలించు"</string>
<string name="action_resize" msgid="1802976324781771067">"పరిమాణం మార్చు"</string>
@@ -150,7 +150,7 @@
<string name="action_decrease_width" msgid="1374549771083094654">"వెడల్పును తగ్గించు"</string>
<string name="action_decrease_height" msgid="282377193880900022">"ఎత్తును తగ్గించు"</string>
<string name="widget_resized" msgid="9130327887929620">"విడ్జెట్ పరిమాణం వెడల్పు <xliff:g id="NUMBER_0">%1$s</xliff:g>కి, ఎత్తు <xliff:g id="NUMBER_1">%2$s</xliff:g>కి మార్చబడింది"</string>
- <string name="action_deep_shortcut" msgid="2864038805849372848">"సత్వరమార్గాలు"</string>
+ <string name="action_deep_shortcut" msgid="2864038805849372848">"షార్ట్కట్స్"</string>
<string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"షార్ట్కట్లు మరియు నోటిఫికేషన్లు"</string>
<string name="action_dismiss_notification" msgid="5909461085055959187">"తీసివేయి"</string>
<string name="accessibility_close" msgid="2277148124685870734">"మూసివేస్తుంది"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 2c01163..65b46cf 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -149,6 +149,7 @@
<attr name="dbFile" format="string" />
<attr name="defaultLayoutId" format="reference" />
+ <attr name="defaultSplitDisplayLayoutId" format="reference" />
<attr name="demoModeLayoutId" format="reference" />
<attr name="isScalable" format="boolean" />
<attr name="devicePaddingId" format="reference" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8457bd8..86b4e71 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -324,6 +324,7 @@
<dimen name="task_thumbnail_icon_drawable_size">0dp</dimen>
<dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
<dimen name="overview_task_margin">0dp</dimen>
+ <dimen name="overview_task_margin_focused">0dp</dimen>
<dimen name="overview_task_margin_grid">0dp</dimen>
<dimen name="overview_actions_margin_gesture">0dp</dimen>
<dimen name="overview_actions_top_margin_gesture_grid_portrait">0dp</dimen>
diff --git a/res/xml/default_workspace_5x5.xml b/res/xml/default_workspace_5x5.xml
index ccdde2c..b4ac8f6 100644
--- a/res/xml/default_workspace_5x5.xml
+++ b/res/xml/default_workspace_5x5.xml
@@ -94,4 +94,5 @@
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
<favorite launcher:uri="market://details?id=com.android.launcher" />
</resolve>
+
</favorites>
diff --git a/res/xml/default_workspace_splitdisplay_5x5.xml b/res/xml/default_workspace_splitdisplay_5x5.xml
new file mode 100644
index 0000000..162367b
--- /dev/null
+++ b/res/xml/default_workspace_splitdisplay_5x5.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <!-- Dialer, Messaging, [Maps/Music], Browser, Camera -->
+ <resolve
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
+ <favorite launcher:uri="tel:123" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+ <favorite launcher:uri="sms:" />
+ <favorite launcher:uri="smsto:" />
+ <favorite launcher:uri="mms:" />
+ <favorite launcher:uri="mmsto:" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0" >
+ <favorite
+ launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+ <favorite launcher:uri="http://www.example.com/" />
+ </resolve>
+
+ <resolve
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" >
+ <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+ </resolve>
+
+ <!-- Bottom row -->
+ <resolve
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="-1" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+ <favorite launcher:uri="mailto:" />
+
+ </resolve>
+
+ <resolve
+ launcher:screen="0"
+ launcher:x="1"
+ launcher:y="-1" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+ <favorite launcher:uri="#Intent;type=images/*;end" />
+
+ </resolve>
+
+ <resolve
+ launcher:screen="0"
+ launcher:x="4"
+ launcher:y="-1" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+ <favorite launcher:uri="market://details?id=com.android.launcher" />
+ </resolve>
+
+ <!-- Placeholder before we add page pairing in b/196376162 -->
+ <resolve
+ launcher:screen="1"
+ launcher:x="0"
+ launcher:y="-4" >
+ <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+ <favorite launcher:uri="#Intent;type=images/*;end" />
+ </resolve>
+
+</favorites>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 256999c..d8ee6f2 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -105,7 +105,8 @@
launcher:numFolderColumns="4"
launcher:numHotseatIcons="5"
launcher:dbFile="launcher.db"
- launcher:defaultLayoutId="@xml/default_workspace_5x5" >
+ launcher:defaultLayoutId="@xml/default_workspace_5x5"
+ launcher:defaultSplitDisplayLayoutId="@xml/default_workspace_splitdisplay_5x5" >
<display-option
launcher:name="Large Phone"
diff --git a/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java b/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java
deleted file mode 100644
index 1090d1e..0000000
--- a/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.os.UserHandle;
-import android.util.Size;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.testing.TestActivity;
-import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class CachingWidgetPreviewLoaderTest {
- private final Size SIZE_10_10 = new Size(10, 10);
- private final Size SIZE_20_20 = new Size(20, 20);
- private static final String TEST_PACKAGE = "com.example.test";
- private final ComponentName TEST_PROVIDER =
- new ComponentName(TEST_PACKAGE, ".WidgetProvider");
- private final ComponentName TEST_PROVIDER2 =
- new ComponentName(TEST_PACKAGE, ".WidgetProvider2");
- private final Bitmap BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
- private final Bitmap BITMAP2 = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888);
-
-
- @Mock private CancellationSignal mCancellationSignal;
- @Mock private WidgetPreviewLoader mDelegate;
- @Mock private IconCache mIconCache;
- @Mock private DeviceProfile mDeviceProfile;
- @Mock private LauncherAppWidgetProviderInfo mProviderInfo;
- @Mock private LauncherAppWidgetProviderInfo mProviderInfo2;
- @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback;
- @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback2;
- @Captor private ArgumentCaptor<WidgetPreviewLoadedCallback> mCallbackCaptor;
-
- private TestActivity mTestActivity;
- private CachingWidgetPreviewLoader mLoader;
- private WidgetItem mWidgetItem;
- private WidgetItem mWidgetItem2;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mLoader = new CachingWidgetPreviewLoader(mDelegate);
-
- mTestActivity = Robolectric.buildActivity(TestActivity.class).setup().get();
- mTestActivity.setDeviceProfile(mDeviceProfile);
-
- when(mDelegate.loadPreview(any(), any(), any(), any())).thenReturn(mCancellationSignal);
-
- mProviderInfo.provider = TEST_PROVIDER;
- when(mProviderInfo.getProfile()).thenReturn(new UserHandle(0));
-
- mProviderInfo2.provider = TEST_PROVIDER2;
- when(mProviderInfo2.getProfile()).thenReturn(new UserHandle(0));
-
- InvariantDeviceProfile testProfile = new InvariantDeviceProfile();
- testProfile.numRows = 5;
- testProfile.numColumns = 5;
-
- mWidgetItem = new WidgetItem(mProviderInfo, testProfile, mIconCache);
- mWidgetItem2 = new WidgetItem(mProviderInfo2, testProfile, mIconCache);
- }
-
- @Test
- public void getPreview_notInCache_shouldReturnNull() {
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
- }
-
- @Test
- public void getPreview_notInCache_shouldNotCallDelegate() {
- mLoader.getPreview(mWidgetItem, SIZE_10_10);
-
- verifyZeroInteractions(mDelegate);
- }
-
- @Test
- public void getPreview_inCache_shouldReturnCachedBitmap() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- }
-
- @Test
- public void getPreview_otherSizeInCache_shouldReturnNull() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isNull();
- }
-
- @Test
- public void getPreview_otherItemInCache_shouldReturnNull() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.getPreview(mWidgetItem2, SIZE_10_10)).isNull();
- }
-
- @Test
- public void getPreview_shouldStoreMultipleSizesPerItem() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP2);
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isEqualTo(BITMAP2);
- }
-
- @Test
- public void loadPreview_notInCache_shouldStartLoading() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- verify(mDelegate).loadPreview(eq(mTestActivity), eq(mWidgetItem), eq(SIZE_10_10), any());
- verifyZeroInteractions(mPreviewLoadedCallback);
- }
-
- @Test
- public void loadPreview_thenLoaded_shouldCallBack() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- loaderCallback.onPreviewLoaded(BITMAP);
-
- verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
- }
-
- @Test
- public void loadPreview_thenCancelled_shouldCancelDelegateRequest() {
- CancellationSignal cancellationSignal =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- cancellationSignal.cancel();
-
- verify(mCancellationSignal).cancel();
- verifyZeroInteractions(mPreviewLoadedCallback);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
- }
-
- @Test
- public void loadPreview_thenCancelled_otherCallListening_shouldNotCancelDelegateRequest() {
- CancellationSignal cancellationSignal1 =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- cancellationSignal1.cancel();
-
- verifyZeroInteractions(mCancellationSignal);
- }
-
- @Test
- public void loadPreview_thenCancelled_otherCallListening_loaded_shouldCallBackToNonCancelled() {
- CancellationSignal cancellationSignal1 =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- cancellationSignal1.cancel();
- loaderCallback.onPreviewLoaded(BITMAP);
-
- verifyZeroInteractions(mPreviewLoadedCallback);
- verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- }
-
- @Test
- public void loadPreview_thenCancelled_bothCallsCancelled_shouldCancelDelegateRequest() {
- CancellationSignal cancellationSignal1 =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- CancellationSignal cancellationSignal2 =
- mLoader.loadPreview(
- mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- cancellationSignal1.cancel();
- cancellationSignal2.cancel();
-
- verify(mCancellationSignal).cancel();
- verifyZeroInteractions(mPreviewLoadedCallback);
- verifyZeroInteractions(mPreviewLoadedCallback2);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
- }
-
- @Test
- public void loadPreview_multipleCallbacks_shouldOnlyCallDelegateOnce() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- verify(mDelegate).loadPreview(any(), any(), any(), any());
- }
-
- @Test
- public void loadPreview_multipleCallbacks_shouldForwardResultToEachCallback() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- loaderCallback.onPreviewLoaded(BITMAP);
-
- verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
- verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
- }
-
- @Test
- public void loadPreview_inCache_shouldCallBackImmediately() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- reset(mDelegate);
-
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
- verifyZeroInteractions(mDelegate);
- }
-
- @Test
- public void loadPreview_thenLoaded_thenCancelled_shouldNotRemovePreviewFromCache() {
- CancellationSignal cancellationSignal =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
- loaderCallback.onPreviewLoaded(BITMAP);
-
- cancellationSignal.cancel();
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- }
-
- @Test
- public void isPreviewLoaded_notLoaded_shouldReturnFalse() {
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void isPreviewLoaded_otherSizeLoaded_shouldReturnFalse() {
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void isPreviewLoaded_otherItemLoaded_shouldReturnFalse() {
- loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void isPreviewLoaded_loaded_shouldReturnTrue() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isTrue();
- }
-
- @Test
- public void clearPreviews_notInCache_shouldBeNoOp() {
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearPreviews_inCache_shouldRemovePreview() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearPreviews_inCache_multipleSizes_shouldRemoveAllSizes() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
- }
-
- @Test
- public void clearPreviews_inCache_otherItems_shouldOnlyRemoveSpecifiedItems() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isTrue();
- }
-
- @Test
- public void clearPreviews_inCache_otherItems_shouldRemoveAllSpecifiedItems() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
- mLoader.clearPreviews(Arrays.asList(mWidgetItem, mWidgetItem2));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearPreviews_loading_shouldCancelLoad() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- verify(mCancellationSignal).cancel();
- }
-
- @Test
- public void clearAll_cacheEmpty_shouldBeNoOp() {
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearAll_inCache_shouldRemovePreview() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearAll_inCache_multipleSizes_shouldRemoveAllSizes() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
- }
-
- @Test
- public void clearAll_inCache_multipleItems_shouldRemoveAll() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
- loadPreviewIntoCache(mWidgetItem2, SIZE_20_20, BITMAP);
-
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_20_20)).isFalse();
- }
-
- @Test
- public void clearAll_loading_shouldCancelLoad() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- mLoader.clearAll();
-
- verify(mCancellationSignal).cancel();
- }
-
- private void loadPreviewIntoCache(WidgetItem widgetItem, Size size, Bitmap bitmap) {
- reset(mDelegate);
- mLoader.loadPreview(mTestActivity, widgetItem, size, ignored -> {});
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- loaderCallback.onPreviewLoaded(bitmap);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index fb44ca1..12aac8b 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -39,7 +39,6 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -64,7 +63,6 @@
private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";
@Mock private LayoutInflater mMockLayoutInflater;
- @Mock private DatabaseWidgetPreviewLoader mMockWidgetCache;
@Mock private RecyclerView.AdapterDataObserver mListener;
@Mock private IconCache mIconCache;
@@ -81,7 +79,7 @@
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
mUserHandle = Process.myUserHandle();
- mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
+ mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater,
mIconCache, () -> 0, null, null);
mAdapter.registerAdapterDataObserver(mListener);
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index b7d7788..fa000c0 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
+import static java.util.Collections.EMPTY_LIST;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -116,7 +118,7 @@
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -134,7 +136,7 @@
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
widgetsListHeader.callOnClick();
verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index 2b4cea0..b18c8b7 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
+import static java.util.Collections.EMPTY_LIST;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -116,7 +118,7 @@
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -135,7 +137,7 @@
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
widgetsListHeader.callOnClick();
verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 9f66fb7..cb38c6f 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.doAnswer;
import static org.robolectric.Shadows.shadowOf;
+import static java.util.Collections.EMPTY_LIST;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -44,7 +46,6 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
@@ -111,7 +112,6 @@
LayoutInflater.from(mTestActivity),
mOnIconClickListener,
mOnLongClickListener,
- new CachingWidgetPreviewLoader(mWidgetPreviewLoader),
new WidgetsListDrawableFactory(mTestActivity));
}
@@ -128,13 +128,13 @@
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
shadowOf(getMainLooper()).idle();
// THEN the table container has one row, which contains 3 widgets.
// View: .SampleWidget0 | .SampleWidget1 | .SampleWidget2
- assertThat(viewHolder.mTableContainer.getChildCount()).isEqualTo(1);
- TableRow row = (TableRow) viewHolder.mTableContainer.getChildAt(0);
+ assertThat(viewHolder.tableContainer.getChildCount()).isEqualTo(1);
+ TableRow row = (TableRow) viewHolder.tableContainer.getChildAt(0);
assertThat(row.getChildCount()).isEqualTo(3);
// Widget 0 label is .SampleWidget0.
assertWidgetCellWithLabel(row.getChildAt(0), ".SampleWidget0");
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 4979b40..e080537 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -64,7 +64,8 @@
TYPE_OPTIONS_POPUP,
TYPE_ICON_SURFACE,
TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP,
- TYPE_WIDGETS_EDUCATION_DIALOG
+ TYPE_WIDGETS_EDUCATION_DIALOG,
+ TYPE_TASKBAR_EDUCATION_DIALOG
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
@@ -87,18 +88,20 @@
public static final int TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP = 1 << 14;
public static final int TYPE_WIDGETS_EDUCATION_DIALOG = 1 << 15;
+ public static final int TYPE_TASKBAR_EDUCATION_DIALOG = 1 << 16;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
| TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
- | TYPE_WIDGETS_EDUCATION_DIALOG;
+ | TYPE_WIDGETS_EDUCATION_DIALOG | TYPE_TASKBAR_EDUCATION_DIALOG;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
- | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG;
+ | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG
+ | TYPE_TASKBAR_EDUCATION_DIALOG;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index eb058e8..3010be1 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -17,7 +17,6 @@
package com.android.launcher3;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.launcher3.ResourceUtils.pxFromDp;
import static com.android.launcher3.Utilities.dpiFromPx;
@@ -33,11 +32,8 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
import android.util.DisplayMetrics;
import android.view.Surface;
-import android.view.WindowInsets;
-import android.view.WindowManager;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
@@ -183,6 +179,7 @@
// Overview
public final boolean overviewShowAsGrid;
public int overviewTaskMarginPx;
+ public int overviewTaskMarginGridPx;
public int overviewTaskIconSizePx;
public int overviewTaskIconDrawableSizePx;
public int overviewTaskIconDrawableSizeGridPx;
@@ -214,6 +211,8 @@
// Taskbar
public boolean isTaskbarPresent;
+ // Whether Taskbar will inset the bottom of apps by taskbarSize.
+ public boolean isTaskbarPresentInApps;
public int taskbarSize;
// How much of the bottom inset is due to Taskbar rather than other system elements.
public int nonOverlappingTaskbarInset;
@@ -221,6 +220,7 @@
// DragController
public int flingToDeleteThresholdVelocity;
+ /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
boolean useTwoPanels) {
@@ -267,13 +267,7 @@
// Taskbar will be added later, but provides bottom insets that we should subtract
// from availableHeightPx.
taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size);
- WindowInsets windowInsets =
- context.createWindowContext(
- context.getSystemService(DisplayManager.class).getDisplay(mInfo.id),
- TYPE_APPLICATION, null)
- .getSystemService(WindowManager.class)
- .getCurrentWindowMetrics().getWindowInsets();
- nonOverlappingTaskbarInset = taskbarSize - windowInsets.getSystemWindowInsetBottom();
+ nonOverlappingTaskbarInset = taskbarSize - windowBounds.insets.bottom;
if (nonOverlappingTaskbarInset > 0) {
nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
}
@@ -361,8 +355,9 @@
overviewShowAsGrid = isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
overviewTaskMarginPx = overviewShowAsGrid
- ? res.getDimensionPixelSize(R.dimen.overview_task_margin_grid)
+ ? res.getDimensionPixelSize(R.dimen.overview_task_margin_focused)
: res.getDimensionPixelSize(R.dimen.overview_task_margin);
+ overviewTaskMarginGridPx = res.getDimensionPixelSize(R.dimen.overview_task_margin_grid);
overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
overviewTaskIconDrawableSizePx =
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
@@ -790,7 +785,7 @@
Point padding = getTotalWorkspacePadding();
// availableWidthPx is the screen width of the device. In 2 panels mode, each panel should
// only have half of the screen width. In addition, there is only cellLayoutPadding in the
- // left side of the left panel and the right side of the right panel. There is no
+ // left side of the left most panel and the right most side of the right panel. There is no
// cellLayoutPadding in the middle.
int screenWidthPx = isTwoPanels
? availableWidthPx / 2 - padding.x - cellLayoutPaddingLeftRightPx
@@ -1089,6 +1084,7 @@
writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
+ writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize));
writer.println(prefix + pxToDpStr("nonOverlappingTaskbarInset",
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 1fc8958..2e14823 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -653,8 +653,10 @@
numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
- defaultLayoutId = a.getResourceId(
- R.styleable.GridDisplayOption_defaultLayoutId, 0);
+ defaultLayoutId = a.getResourceId(isSplitDisplay && a.hasValue(
+ R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId)
+ ? R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId
+ : R.styleable.GridDisplayOption_defaultLayoutId, 0);
demoModeLayoutId = a.getResourceId(
R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1ebfda1..4d5cc5e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -36,7 +36,6 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -2099,19 +2098,12 @@
? mWorkspace.getCurrentPageScreenIds() : mPagesToBindSynchronously;
IntArray actualIds = new IntArray();
- if (mDeviceProfile.isTwoPanels) {
- actualIds.add(LEFT_PANEL_ID);
- } else {
- visibleIds.remove(LEFT_PANEL_ID);
- }
IntSet result = new IntSet();
if (visibleIds.isEmpty()) {
return result;
}
for (int id : orderedScreenIds.toArray()) {
- if (id != LEFT_PANEL_ID) {
- actualIds.add(id);
- }
+ actualIds.add(id);
}
int firstId = visibleIds.getArray().get(0);
if (actualIds.contains(firstId)) {
@@ -2119,7 +2111,7 @@
if (mDeviceProfile.isTwoPanels) {
int index = actualIds.indexOf(firstId);
- int nextIndex = ((int) (index / 2)) * 2;
+ int nextIndex = (index / 2) * 2;
if (nextIndex == index) {
nextIndex++;
}
@@ -2176,12 +2168,7 @@
@Override
public void bindScreens(IntArray orderedScreenIds) {
- // Make sure the first screen is at the start if there's no widget panel,
- // or on the second place if the first is the widget panel
- boolean isLeftPanelShown =
- mWorkspace.mWorkspaceScreens.containsKey(LEFT_PANEL_ID);
- int firstScreenPosition = isLeftPanelShown && orderedScreenIds.size() > 1 ? 1 : 0;
-
+ int firstScreenPosition = 0;
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
@@ -2207,11 +2194,6 @@
continue;
}
- if (screenId == LEFT_PANEL_ID) {
- // No need to bind the left panel, as its always bound.
- continue;
- }
-
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
}
@@ -2287,11 +2269,6 @@
continue;
}
- // Skip if the item is on the left widget panel but the panel is not shown
- if (item.screenId == LEFT_PANEL_ID && !getDeviceProfile().isTwoPanels) {
- continue;
- }
-
final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 3d6be69..702b73a 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -48,7 +48,6 @@
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.custom.CustomWidgetManager;
public class LauncherAppState {
@@ -64,7 +63,6 @@
private final LauncherModel mModel;
private final IconProvider mIconProvider;
private final IconCache mIconCache;
- private final DatabaseWidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
private final RunnableList mOnTerminateCallback = new RunnableList();
@@ -139,7 +137,6 @@
mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context));
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
- mWidgetCache = new DatabaseWidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
mOnTerminateCallback.add(mIconCache::close);
}
@@ -155,7 +152,6 @@
LauncherIcons.clearPool();
mIconCache.updateIconParams(
mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
- mWidgetCache.refresh();
mModel.forceReload();
}
@@ -181,10 +177,6 @@
return mModel;
}
- public DatabaseWidgetPreviewLoader getWidgetCache() {
- return mWidgetCache;
- }
-
public InvariantDeviceProfile getInvariantDeviceProfile() {
return mInvariantDeviceProfile;
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 6966abf..7b6a5bf 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -72,6 +72,7 @@
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -383,7 +384,13 @@
loaderResults.bindWidgets();
return true;
} else {
- startLoaderForResults(loaderResults);
+ stopLoader();
+ mLoaderTask = new LoaderTask(
+ mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
+
+ // Always post the loader task, instead of running directly
+ // (even on same thread) so that we exit any nested synchronized blocks
+ MODEL_EXECUTOR.post(mLoaderTask);
}
}
}
@@ -406,25 +413,17 @@
}
}
- public void startLoaderForResults(LoaderResults results) {
+ /**
+ * Loads the model if not loaded
+ * @param callback called with the data model upon successful load or null on model thread.
+ */
+ public void loadAsync(Consumer<BgDataModel> callback) {
synchronized (mLock) {
- stopLoader();
- mLoaderTask = new LoaderTask(
- mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results);
-
- // Always post the loader task, instead of running directly (even on same thread) so
- // that we exit any nested synchronized blocks
- MODEL_EXECUTOR.post(mLoaderTask);
- }
- }
-
- public void startLoaderForResultsIfNotLoaded(LoaderResults results) {
- synchronized (mLock) {
- if (!isModelLoaded()) {
- Log.d(TAG, "Workspace not loaded, loading now");
- startLoaderForResults(results);
+ if (!mModelLoaded && !mIsLoaderTaskRunning) {
+ startLoader();
}
}
+ MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null));
}
@Override
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index eb3f94c..696e897 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -289,7 +289,7 @@
newPage = Utilities.boundToRange(newPage, 0, getPageCount() - 1);
if (getPanelCount() > 1) {
- // Always return left panel as new page
+ // Always return left most panel as new page
newPage = getLeftmostVisiblePageForIndex(newPage);
}
return newPage;
@@ -774,7 +774,7 @@
if (panelCount > 1) {
for (int i = 0; i < childCount; i++) {
- // In case we have multiple panels, always use left panel's page scroll for all
+ // In case we have multiple panels, always use left most panel's page scroll for all
// panels on the screen.
int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
if (outPageScrolls[i] != adjustedScroll) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 3bfa1e2..d162abd 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -550,9 +550,6 @@
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
- if (isTwoPanelEnabled()) {
- insertNewWorkspaceScreen(Workspace.LEFT_PANEL_ID, getChildCount());
- }
// Add the first page
CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 326e3c3..d6302ce 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -32,8 +32,6 @@
int EXTRA_EMPTY_SCREEN_ID = -201;
// The is the first screen. It is always present, even if its empty.
int FIRST_SCREEN_ID = 0;
- // This panel is shown on the first page if the panel count is greater than 1.
- int LEFT_PANEL_ID = -777;
/**
* At bind time, we use the rank (screenId) to compute x and y for hotseat items.
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0fb5e77..9faac5b 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -188,8 +188,7 @@
private boolean itemSupportsAccessibleDrag(ItemInfo item) {
if (item instanceof WorkspaceItemInfo) {
// Support the action unless the item is in a context menu.
- return (item.screenId >= 0 || item.screenId == Workspace.LEFT_PANEL_ID)
- && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
return (item instanceof LauncherAppWidgetInfo)
|| (item instanceof FolderInfo);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index da701a8..38a957d 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -219,6 +219,9 @@
public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
"ENABLE_TASKBAR", true, "Allows a system Taskbar to be shown on larger devices.");
+ public static final BooleanFlag ENABLE_TASKBAR_EDU = getDebugFlag("ENABLE_TASKBAR_EDU", false,
+ "Enables showing taskbar education the first time an app is opened.");
+
public static final BooleanFlag ENABLE_OVERVIEW_GRID = getDebugFlag(
"ENABLE_OVERVIEW_GRID", true, "Uses grid overview layout. "
+ "Only applicable on large screen devices.");
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 92ed18a..466b268 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -286,9 +286,7 @@
@Override
protected void onPostExecute(WidgetItem item) {
- mWidgetCell.setPreviewSize(item);
- mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
- mWidgetCell.ensurePreview();
+ mWidgetCell.applyFromCellItem(item);
}
}.executeOnExecutor(MODEL_EXECUTOR);
// TODO: Create a worker looper executor and reuse that everywhere.
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 55995f2..a96de31 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -266,22 +266,22 @@
mHotseat = mRootView.findViewById(R.id.hotseat);
mHotseat.resetLayout(false);
- if (mDp.isTwoPanels) {
- CellLayout leftPanel = mRootView.findViewById(R.id.workspace_left);
- leftPanel.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
- mDp.workspacePadding.top,
- mDp.workspacePadding.right,
- mDp.workspacePadding.bottom);
- mWorkspaceScreens.put(LEFT_PANEL_ID, leftPanel);
- }
-
CellLayout firstScreen = mRootView.findViewById(R.id.workspace);
- firstScreen.setPadding(mDp.workspacePadding.left,
+ firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
mDp.workspacePadding.top,
- mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+ mDp.workspacePadding.right,
mDp.workspacePadding.bottom);
mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen);
+ if (mDp.isTwoPanels) {
+ CellLayout rightPanel = mRootView.findViewById(R.id.workspace_right);
+ rightPanel.setPadding(mDp.workspacePadding.left,
+ mDp.workspacePadding.top,
+ mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+ mDp.workspacePadding.bottom);
+ mWorkspaceScreens.put(PreviewSurfaceRenderer.SECOND_SCREEN_ID, rightPanel);
+ }
+
if (Utilities.ATLEAST_S) {
WallpaperColors wallpaperColors = wallpaperColorsOverride != null
? wallpaperColorsOverride
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index c7448dc..af006d6 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -49,7 +49,6 @@
import com.android.launcher3.model.GridSizeMigrationTaskV2;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
-import com.android.launcher3.model.ModelPreload;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
@@ -67,6 +66,9 @@
private static final int FADE_IN_ANIMATION_DURATION = 200;
+ // The is the second screen. It is always present in two panel, even if its empty.
+ static final int SECOND_SCREEN_ID = 1;
+
private static final String KEY_HOST_TOKEN = "host_token";
private static final String KEY_VIEW_WIDTH = "width";
private static final String KEY_VIEW_HEIGHT = "height";
@@ -164,11 +166,14 @@
@Override
public void run() {
DeviceProfile deviceProfile = mIdp.getDeviceProfile(previewContext);
- String query = (deviceProfile.isTwoPanels ? LauncherSettings.Favorites.SCREEN
- + " = " + Workspace.LEFT_PANEL_ID + " or " : "")
- + LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
+ String query =
+ LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
+ " or " + LauncherSettings.Favorites.CONTAINER + " = "
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ if (deviceProfile.isTwoPanels) {
+ query += " or " + LauncherSettings.Favorites.SCREEN + " = "
+ + SECOND_SCREEN_ID;
+ }
loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
query);
@@ -179,18 +184,13 @@
}
}.run();
} else {
- new ModelPreload() {
-
- @Override
- public void onComplete(boolean isSuccess) {
- if (isSuccess) {
- MAIN_EXECUTOR.execute(() ->
- renderView(inflationContext, getBgDataModel(), null));
- } else {
- Log.e(TAG, "Model loading failed");
- }
+ LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
+ if (dataModel != null) {
+ MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null));
+ } else {
+ Log.e(TAG, "Model loading failed");
}
- }.start(inflationContext);
+ });
}
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 4f12d0b..a13fa55 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -16,7 +16,6 @@
package com.android.launcher3.model;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
@@ -297,7 +296,7 @@
int screenCount = workspaceScreens.size();
// First check the preferred screen.
- IntSet screensToExclude = IntSet.wrap(LEFT_PANEL_ID);
+ IntSet screensToExclude = new IntSet();
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
screensToExclude.add(FIRST_SCREEN_ID);
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index e64b25c..e7d0749 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -38,7 +38,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
@@ -214,9 +213,6 @@
// Migrate workspace.
// First we create a collection of the screens
List<Integer> screens = new ArrayList<>();
- if (idp.getDeviceProfile(mContext).isTwoPanels) {
- screens.add(Workspace.LEFT_PANEL_ID);
- }
for (int screenId = 0; screenId <= mDestReader.mLastScreenId; screenId++) {
screens.add(screenId);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 41a760b..f4a0eb8 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,7 +16,6 @@
package com.android.launcher3.model;
-import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -179,11 +178,7 @@
// Screen set is never empty
IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
final int firstScreen = allScreens.get(0);
-
IntSet firstScreens = IntSet.wrap(firstScreen);
- if (firstScreen == LEFT_PANEL_ID && allScreens.size() >= 2) {
- firstScreens.add(allScreens.get(1));
- }
filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
new ArrayList<>() /* otherScreenItems are ignored */);
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
deleted file mode 100644
index 756b7da..0000000
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.model;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-
-import java.util.concurrent.Executor;
-
-/**
- * Utility class to preload LauncherModel
- */
-public class ModelPreload implements ModelUpdateTask {
-
- private static final String TAG = "ModelPreload";
-
- private LauncherAppState mApp;
- private LauncherModel mModel;
- private BgDataModel mBgDataModel;
- private AllAppsList mAllAppsList;
-
- @Override
- public final void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
- AllAppsList allAppsList, Executor uiExecutor) {
- mApp = app;
- mModel = model;
- mBgDataModel = dataModel;
- mAllAppsList = allAppsList;
- }
-
- @Override
- public final void run() {
- mModel.startLoaderForResultsIfNotLoaded(
- new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
- MODEL_EXECUTOR.post(() -> {
- Log.d(TAG, "Preload completed : " + mModel.isModelLoaded());
- onComplete(mModel.isModelLoaded());
- });
- }
-
- public BgDataModel getBgDataModel() {
- return mBgDataModel;
- }
-
- /**
- * Called when the task is complete
- */
- @WorkerThread
- public void onComplete(boolean isSuccess) { }
-
- public void start(Context context) {
- LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(this);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 82b0f7c..83fb3d1 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -123,7 +123,6 @@
iconCache.updateIconsForPkg(packages[i], mUser);
activitiesLists.put(
packages[i], appsList.updatePackage(context, packages[i], mUser));
- app.getWidgetCache().removePackage(packages[i], mUser);
// The update may have changed which shortcuts/widgets are available.
// Refresh the widgets for the package if we have an activity running.
@@ -148,7 +147,6 @@
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
appsList.removePackage(packages[i], mUser);
- app.getWidgetCache().removePackage(packages[i], mUser);
}
flagOp = FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
index 6215827..31436c4 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -22,6 +22,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
/**
* Creates and populates views with data
@@ -46,7 +47,7 @@
V newViewHolder(ViewGroup parent);
/** Populate UI references in {@link ViewHolder} with data. */
- void bindViewHolder(V viewHolder, T data, @ListPosition int position);
+ void bindViewHolder(V viewHolder, T data, @ListPosition int position, List<Object> payloads);
/**
* Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index b34af97..24d3fd4 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -18,6 +18,7 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
@@ -27,12 +28,14 @@
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
+import android.util.Log;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.TestProtocol;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -253,6 +256,9 @@
if (listener != null) {
animation.addListener(listener);
}
+ if (TestProtocol.sDebugTracing && state == NORMAL) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "goToStateAnimated: " + state);
+ }
mUiHandler.post(new StartAnimRunnable(animation));
}
@@ -328,11 +334,17 @@
@Override
public void onAnimationStart(Animator animation) {
// Change the internal state only when the transition actually starts
+ if (TestProtocol.sDebugTracing && state == NORMAL) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "onAnimationStart: " + state);
+ }
onStateTransitionStart(state);
}
@Override
public void onAnimationSuccess(Animator animator) {
+ if (TestProtocol.sDebugTracing && state == NORMAL) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "onAnimationEnd: " + state);
+ }
onStateTransitionEnd(state);
}
};
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 1c5b31b..c484811 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -119,4 +119,5 @@
public static final String FALLBACK_ACTIVITY_NO_SET = "b/181019015";
public static final String THIRD_PARTY_LAUNCHER_NOT_SET = "b/187080582";
public static final String TASK_VIEW_ID_CRASH = "b/195430732";
+ public static final String L3_SWIPE_TO_HOME = "b/192018189";
}
diff --git a/src/com/android/launcher3/util/HorizontalInsettableView.java b/src/com/android/launcher3/util/HorizontalInsettableView.java
new file mode 100644
index 0000000..7979bc0
--- /dev/null
+++ b/src/com/android/launcher3/util/HorizontalInsettableView.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+/**
+ * Allows the implementing view to add insets to the left and right.
+ */
+public interface HorizontalInsettableView {
+
+ /**
+ * Sets left and right insets for the view so it looks like the width of the view is
+ * reduced when inset is increased.
+ *
+ * The inset is calculated based on the width of the view: e.g. when the width of
+ * the view is 100px then if we apply 0.15f horizontal inset percentage the rendered width
+ * of the view will be 70px with 15px of padding on the left and right sides.
+ *
+ * @param insetPercentage width percentage to inset the content from the left and from the right
+ */
+ void setHorizontalInsets(float insetPercentage);
+
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 40bc9c3..cf1467a 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -38,6 +38,7 @@
public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
+ public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
/**
* Events that either have happened or have not (booleans).
@@ -45,7 +46,8 @@
@StringDef(value = {
HOME_BOUNCE_SEEN,
HOTSEAT_LONGPRESS_TIP_SEEN,
- SEARCH_EDU_SEEN
+ SEARCH_EDU_SEEN,
+ TASKBAR_EDU_SEEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventBoolKey {
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 573c8bd..1f1db9d 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -18,6 +18,8 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.graphics.Rect;
+
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
@@ -82,4 +84,25 @@
mStageType = stageType;
}
}
+
+ public static class StagedSplitBounds {
+ public final Rect mLeftTopBounds;
+ public final Rect mRightBottomBounds;
+ public final Rect mDividerBounds;
+
+
+ public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, Rect dividerBounds) {
+ mLeftTopBounds = leftTopBounds;
+ mRightBottomBounds = rightBottomBounds;
+ mDividerBounds = dividerBounds;
+ }
+ }
+
+ public static class StagedSplitTaskPosition {
+ public int taskId = -1;
+ @StagePosition
+ public int stagePosition = STAGE_POSITION_UNDEFINED;
+ @StageType
+ public int stageType = STAGE_TYPE_UNDEFINED;
+ }
}
diff --git a/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java b/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java
deleted file mode 100644
index afceadd..0000000
--- a/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.collection.ArrayMap;
-import androidx.collection.ArraySet;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.ComponentKey;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/** Wrapper around {@link DatabaseWidgetPreviewLoader} that contains caching logic. */
-public class CachingWidgetPreviewLoader implements WidgetPreviewLoader {
-
- @NonNull private final WidgetPreviewLoader mDelegate;
- @NonNull private final Map<ComponentKey, Map<Size, CacheResult>> mCache = new ArrayMap<>();
-
- public CachingWidgetPreviewLoader(@NonNull WidgetPreviewLoader delegate) {
- mDelegate = delegate;
- }
-
- /** Returns whether the preview is loaded for the item and size. */
- public boolean isPreviewLoaded(@NonNull WidgetItem item, @NonNull Size previewSize) {
- return getPreview(item, previewSize) != null;
- }
-
- /** Returns the cached preview for the item and size, or null if there is none. */
- @Nullable
- public Bitmap getPreview(@NonNull WidgetItem item, @NonNull Size previewSize) {
- CacheResult cacheResult = getCacheResult(item, previewSize);
- if (cacheResult instanceof CacheResult.Loaded) {
- return ((CacheResult.Loaded) cacheResult).mBitmap;
- } else {
- return null;
- }
- }
-
- @NonNull
- private CacheResult getCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
- synchronized (mCache) {
- Map<Size, CacheResult> cacheResults = mCache.get(toComponentKey(item));
- if (cacheResults == null) {
- return CacheResult.MISS;
- }
-
- return cacheResults.getOrDefault(previewSize, CacheResult.MISS);
- }
- }
-
- /**
- * Puts the result in the cache for the item and size. Returns the value previously in the
- * cache, or null if there was none.
- */
- @Nullable
- private CacheResult putCacheResult(
- @NonNull WidgetItem item,
- @NonNull Size previewSize,
- @Nullable CacheResult cacheResult) {
- ComponentKey key = toComponentKey(item);
- synchronized (mCache) {
- Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
- CacheResult previous;
- if (cacheResult == null) {
- previous = cacheResults.remove(previewSize);
- if (cacheResults.isEmpty()) {
- mCache.remove(key);
- } else {
- previous = cacheResults.put(previewSize, cacheResult);
- mCache.put(key, cacheResults);
- }
- } else {
- previous = cacheResults.put(previewSize, cacheResult);
- mCache.put(key, cacheResults);
- }
- return previous;
- }
- }
-
- private void removeCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
- ComponentKey key = toComponentKey(item);
- synchronized (mCache) {
- Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
- cacheResults.remove(previewSize);
- mCache.put(key, cacheResults);
- }
- }
-
- /**
- * Gets the preview for the widget item and size, using the value in the cache if stored.
- *
- * @return a {@link CancellationSignal}, which can cancel the request before it loads
- */
- @Override
- @UiThread
- @NonNull
- public CancellationSignal loadPreview(
- @NonNull BaseActivity activity, @NonNull WidgetItem item, @NonNull Size previewSize,
- @NonNull WidgetPreviewLoadedCallback callback) {
- CancellationSignal signal = new CancellationSignal();
- signal.setOnCancelListener(() -> {
- synchronized (mCache) {
- CacheResult cacheResult = getCacheResult(item, previewSize);
- if (!(cacheResult instanceof CacheResult.Loading)) {
- // If the key isn't actively loading, then this is a no-op. Cancelling loading
- // shouldn't clear the cache if we've already loaded.
- return;
- }
-
- CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
- CacheResult.Loading updated = prev.withoutCallback(callback);
-
- if (updated.mCallbacks.isEmpty()) {
- // If the last callback was removed, then cancel the underlying request in the
- // delegate.
- prev.mCancellationSignal.cancel();
- removeCacheResult(item, previewSize);
- } else {
- // If there are other callbacks still active, then don't cancel the delegate's
- // request, just remove this callback from the set.
- putCacheResult(item, previewSize, updated);
- }
- }
- });
-
- synchronized (mCache) {
- CacheResult cacheResult = getCacheResult(item, previewSize);
- if (cacheResult instanceof CacheResult.Loaded) {
- // If the bitmap is already present in the cache, invoke the callback immediately.
- callback.onPreviewLoaded(((CacheResult.Loaded) cacheResult).mBitmap);
- return signal;
- }
-
- if (cacheResult instanceof CacheResult.Loading) {
- // If we're already loading the preview for this key, then just add the callback
- // to the set we'll call after it loads.
- CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
- putCacheResult(item, previewSize, prev.withCallback(callback));
- return signal;
- }
-
- CancellationSignal delegateCancellationSignal =
- mDelegate.loadPreview(
- activity,
- item,
- previewSize,
- preview -> {
- CacheResult prev;
- synchronized (mCache) {
- prev = putCacheResult(
- item, previewSize, new CacheResult.Loaded(preview));
- }
- if (prev instanceof CacheResult.Loading) {
- // Notify each stored callback that the preview has loaded.
- ((CacheResult.Loading) prev).mCallbacks
- .forEach(c -> c.onPreviewLoaded(preview));
- } else {
- // If there isn't a loading object in the cache, then we were
- // notified before adding this signal to the cache. Just
- // call back to the provided callback, there can't be others.
- callback.onPreviewLoaded(preview);
- }
- });
- ArraySet<WidgetPreviewLoadedCallback> callbacks = new ArraySet<>();
- callbacks.add(callback);
- putCacheResult(
- item,
- previewSize,
- new CacheResult.Loading(delegateCancellationSignal, callbacks));
- }
-
- return signal;
- }
-
- /** Clears all cached previews for {@code items}, cancelling any in-progress preview loading. */
- public void clearPreviews(Iterable<WidgetItem> items) {
- List<CacheResult> previousCacheResults = new ArrayList<>();
- synchronized (mCache) {
- for (WidgetItem item : items) {
- Map<Size, CacheResult> previousMap = mCache.remove(toComponentKey(item));
- if (previousMap != null) {
- previousCacheResults.addAll(previousMap.values());
- }
- }
- }
-
- for (CacheResult previousCacheResult : previousCacheResults) {
- if (previousCacheResult instanceof CacheResult.Loading) {
- ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
- }
- }
- }
-
- /** Clears all cached previews, cancelling any in-progress preview loading. */
- public void clearAll() {
- List<CacheResult> previousCacheResults;
- synchronized (mCache) {
- previousCacheResults =
- mCache
- .values()
- .stream()
- .flatMap(sizeToResult -> sizeToResult.values().stream())
- .collect(Collectors.toList());
- mCache.clear();
- }
-
- for (CacheResult previousCacheResult : previousCacheResults) {
- if (previousCacheResult instanceof CacheResult.Loading) {
- ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
- }
- }
- }
-
- private abstract static class CacheResult {
- static final CacheResult MISS = new CacheResult() {};
-
- static final class Loading extends CacheResult {
- @NonNull final CancellationSignal mCancellationSignal;
- @NonNull final Set<WidgetPreviewLoadedCallback> mCallbacks;
-
- Loading(@NonNull CancellationSignal cancellationSignal,
- @NonNull Set<WidgetPreviewLoadedCallback> callbacks) {
- mCancellationSignal = cancellationSignal;
- mCallbacks = callbacks;
- }
-
- @NonNull
- Loading withCallback(@NonNull WidgetPreviewLoadedCallback callback) {
- if (mCallbacks.contains(callback)) return this;
- Set<WidgetPreviewLoadedCallback> newCallbacks =
- new ArraySet<>(mCallbacks.size() + 1);
- newCallbacks.addAll(mCallbacks);
- newCallbacks.add(callback);
- return new Loading(mCancellationSignal, newCallbacks);
- }
-
- @NonNull
- Loading withoutCallback(@NonNull WidgetPreviewLoadedCallback callback) {
- if (!mCallbacks.contains(callback)) return this;
- Set<WidgetPreviewLoadedCallback> newCallbacks =
- new ArraySet<>(mCallbacks.size() - 1);
- for (WidgetPreviewLoadedCallback existingCallback : mCallbacks) {
- if (!existingCallback.equals(callback)) {
- newCallbacks.add(existingCallback);
- }
- }
- return new Loading(mCancellationSignal, newCallbacks);
- }
- }
-
- static final class Loaded extends CacheResult {
- @NonNull final Bitmap mBitmap;
-
- Loaded(@NonNull Bitmap bitmap) {
- mBitmap = bitmap;
- }
- }
- }
-
- @NonNull
- private static ComponentKey toComponentKey(@NonNull WidgetItem item) {
- return new ComponentKey(item.componentName, item.user);
- }
-}
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index 4ec7e60..95c3e1e 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -16,21 +16,9 @@
package com.android.launcher3.widget;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -39,72 +27,40 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.CancellationSignal;
+import android.os.Handler;
import android.os.Process;
-import android.os.UserHandle;
import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.Pair;
import android.util.Size;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShadowGenerator;
+import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.SQLiteCacheHelper;
-import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.util.WidgetSizes;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.WeakHashMap;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
-/** {@link WidgetPreviewLoader} that loads preview images from a {@link CacheDb}. */
-public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader {
+/** Utility class to load widget previews */
+public class DatabaseWidgetPreviewLoader {
private static final String TAG = "WidgetPreviewLoader";
- private static final boolean DEBUG = false;
- private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
-
- /**
- * Weak reference objects, do not prevent their referents from being made finalizable,
- * finalized, and then reclaimed.
- * Note: synchronized block used for this variable is expensive and the block should always
- * be posted to a background thread.
- */
- @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>());
-
- private final Context mContext;
- private final IconCache mIconCache;
- private final UserCache mUserCache;
- private final CacheDb mDb;
+ private final BaseActivity mContext;
private final float mPreviewBoxCornerRadius;
- public DatabaseWidgetPreviewLoader(Context context, IconCache iconCache) {
+ public DatabaseWidgetPreviewLoader(BaseActivity context) {
mContext = context;
- mIconCache = iconCache;
- mUserCache = UserCache.INSTANCE.get(context);
- mDb = new CacheDb(context);
float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
mPreviewBoxCornerRadius = previewCornerRadius > 0
? previewCornerRadius
@@ -117,251 +73,29 @@
*
* @return a request id which can be used to cancel the request.
*/
- @Override
@NonNull
- public CancellationSignal loadPreview(
- @NonNull BaseActivity activity,
+ public HandlerRunnable loadPreview(
@NonNull WidgetItem item,
@NonNull Size previewSize,
- @NonNull WidgetPreviewLoadedCallback callback) {
- int previewWidth = previewSize.getWidth();
- int previewHeight = previewSize.getHeight();
- String size = previewWidth + "x" + previewHeight;
- WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
-
- PreviewLoadTask task =
- new PreviewLoadTask(activity, key, item, previewWidth, previewHeight, callback);
- task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
-
- CancellationSignal signal = new CancellationSignal();
- signal.setOnCancelListener(task);
- return signal;
- }
-
- /** Clears the database storing previews. */
- public void refresh() {
- mDb.clear();
- }
-
- /**
- * The DB holds the generated previews for various components. Previews can also have different
- * sizes (landscape vs portrait).
- */
- private static class CacheDb extends SQLiteCacheHelper {
- private static final int DB_VERSION = 9;
-
- private static final String TABLE_NAME = "shortcut_and_widget_previews";
- private static final String COLUMN_COMPONENT = "componentName";
- private static final String COLUMN_USER = "profileId";
- private static final String COLUMN_SIZE = "size";
- private static final String COLUMN_PACKAGE = "packageName";
- private static final String COLUMN_LAST_UPDATED = "lastUpdated";
- private static final String COLUMN_VERSION = "version";
- private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
-
- CacheDb(Context context) {
- super(context, LauncherFiles.WIDGET_PREVIEWS_DB, DB_VERSION, TABLE_NAME);
- }
-
- @Override
- public void onCreateTable(SQLiteDatabase database) {
- database.execSQL("CREATE TABLE IF NOT EXISTS "
- + TABLE_NAME
- + " ("
- + COLUMN_COMPONENT
- + " TEXT NOT NULL, "
- + COLUMN_USER
- + " INTEGER NOT NULL, "
- + COLUMN_SIZE
- + " TEXT NOT NULL, "
- + COLUMN_PACKAGE
- + " TEXT NOT NULL, "
- + COLUMN_LAST_UPDATED
- + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_VERSION
- + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_PREVIEW_BITMAP
- + " BLOB, "
- + "PRIMARY KEY ("
- + COLUMN_COMPONENT
- + ", "
- + COLUMN_USER
- + ", "
- + COLUMN_SIZE
- + ") "
- +
- ");");
- }
- }
-
- @Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
- ContentValues values = new ContentValues();
- values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
- values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user));
- values.put(CacheDb.COLUMN_SIZE, key.mSize);
- values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
- values.put(CacheDb.COLUMN_VERSION, versions[0]);
- values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
- values.put(CacheDb.COLUMN_PREVIEW_BITMAP, GraphicsUtils.flattenBitmap(preview));
- mDb.insertOrReplace(values);
- }
-
- /** Removes the package from the preview database. */
- public void removePackage(String packageName, UserHandle user) {
- removePackage(packageName, user, mUserCache.getSerialNumberForUser(user));
- }
-
- /** Removes the package from the preview database. */
- public void removePackage(String packageName, UserHandle user, long userSerial) {
- synchronized (mPackageVersions) {
- mPackageVersions.remove(packageName);
- }
-
- mDb.delete(
- CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
- new String[]{packageName, Long.toString(userSerial)});
- }
-
- /**
- * Updates the persistent DB:
- * 1. Any preview generated for an old package version is removed
- * 2. Any preview for an absent package is removed
- * This ensures that we remove entries for packages which changed while the launcher was dead.
- *
- * @param packageUser if provided, specifies that list only contains previews for the
- * given package/user, otherwise the list contains all previews
- */
- public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list,
- @Nullable PackageUserKey packageUser) {
- Preconditions.assertWorkerThread();
-
- LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
-
- for (ComponentKey key : list) {
- final long userId = mUserCache.getSerialNumberForUser(key.user);
- HashSet<String> packages = validPackages.get(userId);
- if (packages == null) {
- packages = new HashSet<>();
- validPackages.put(userId, packages);
- }
- packages.add(key.componentName.getPackageName());
- }
-
- LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
- long passedUserId = packageUser == null ? 0
- : mUserCache.getSerialNumberForUser(packageUser.mUser);
- Cursor c = null;
- try {
- c = mDb.query(
- new String[]{CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
- CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
- null, null);
- while (c.moveToNext()) {
- long userId = c.getLong(0);
- String pkg = c.getString(1);
- long lastUpdated = c.getLong(2);
- long version = c.getLong(3);
-
- if (packageUser != null && (!pkg.equals(packageUser.mPackageName)
- || userId != passedUserId)) {
- // This preview is associated with a different package/user, no need to remove.
- continue;
- }
-
- HashSet<String> packages = validPackages.get(userId);
- if (packages != null && packages.contains(pkg)) {
- long[] versions = getPackageVersion(pkg);
- if (versions[0] == version && versions[1] == lastUpdated) {
- // Every thing checks out
- continue;
- }
- }
-
- // We need to delete this package.
- packages = packagesToDelete.get(userId);
- if (packages == null) {
- packages = new HashSet<>();
- packagesToDelete.put(userId, packages);
- }
- packages.add(pkg);
- }
-
- for (int i = 0; i < packagesToDelete.size(); i++) {
- long userId = packagesToDelete.keyAt(i);
- UserHandle user = mUserCache.getUserForSerialNumber(userId);
- for (String pkg : packagesToDelete.valueAt(i)) {
- removePackage(pkg, user, userId);
- }
- }
- } catch (SQLException e) {
- Log.e(TAG, "Error updating widget previews", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- /**
- * Reads the preview bitmap from the DB or null if the preview is not in the DB.
- */
- @Thunk Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle, PreviewLoadTask loadTask) {
- Cursor cursor = null;
- try {
- cursor = mDb.query(
- new String[]{CacheDb.COLUMN_PREVIEW_BITMAP},
- CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND "
- + CacheDb.COLUMN_SIZE + " = ?",
- new String[]{
- key.componentName.flattenToShortString(),
- Long.toString(mUserCache.getSerialNumberForUser(key.user)),
- key.mSize
- });
- // If cancelled, skip getting the blob and decoding it into a bitmap
- if (loadTask.isCancelled()) {
- return null;
- }
- if (cursor.moveToNext()) {
- byte[] blob = cursor.getBlob(0);
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inBitmap = recycle;
- try {
- if (!loadTask.isCancelled()) {
- return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
- }
- } catch (Exception e) {
- return null;
- }
- }
- } catch (SQLException e) {
- Log.w(TAG, "Error loading preview from DB", e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
+ @NonNull Consumer<Bitmap> callback) {
+ Handler handler = Executors.UI_HELPER_EXECUTOR.getHandler();
+ HandlerRunnable<Bitmap> request = new HandlerRunnable<>(handler,
+ () -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()),
+ MAIN_EXECUTOR,
+ callback);
+ Utilities.postAsyncCallback(handler, request);
+ return request;
}
/**
* Returns a generated preview for a widget and if the preview should be saved in persistent
* storage.
- * @param launcher
- * @param item
- * @param recycle
- * @param previewWidth
- * @param previewHeight
- * @return Pair<Bitmap, Boolean>
*/
- private Pair<Bitmap, Boolean> generatePreview(BaseActivity launcher, WidgetItem item,
- Bitmap recycle,
- int previewWidth, int previewHeight) {
+ private Bitmap generatePreview(WidgetItem item, int previewWidth, int previewHeight) {
if (item.widgetInfo != null) {
- return generateWidgetPreview(launcher, item.widgetInfo,
- previewWidth, recycle, null);
+ return generateWidgetPreview(item.widgetInfo, previewWidth, null);
} else {
- return new Pair<>(generateShortcutPreview(launcher, item.activityInfo,
- previewWidth, previewHeight, recycle), false);
+ return generateShortcutPreview(item.activityInfo, previewWidth, previewHeight);
}
}
@@ -369,16 +103,12 @@
* Generates the widget preview from either the {@link WidgetManagerHelper} or cache
* and add badge at the bottom right corner.
*
- * @param launcher
* @param info information about the widget
* @param maxPreviewWidth width of the preview on either workspace or tray
- * @param preview bitmap that can be recycled
* @param preScaledWidthOut return the width of the returned bitmap
- * @return Pair<Bitmap (the preview) , Boolean (should be stored in db)>
*/
- public Pair<Bitmap, Boolean> generateWidgetPreview(BaseActivity launcher,
- LauncherAppWidgetProviderInfo info,
- int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
+ public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
+ int maxPreviewWidth, int[] preScaledWidthOut) {
// Load the preview image if possible
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
@@ -409,117 +139,96 @@
int previewWidth;
int previewHeight;
- boolean savePreviewImage = widgetPreviewExists || info.previewImage == 0;
-
if (widgetPreviewExists && drawable.getIntrinsicWidth() > 0
&& drawable.getIntrinsicHeight() > 0) {
previewWidth = drawable.getIntrinsicWidth();
previewHeight = drawable.getIntrinsicHeight();
} else {
- DeviceProfile dp = launcher.getDeviceProfile();
+ DeviceProfile dp = mContext.getDeviceProfile();
Size widgetSize = WidgetSizes.getWidgetPaddedSizePx(mContext, info.provider, dp, spanX,
spanY);
previewWidth = widgetSize.getWidth();
previewHeight = widgetSize.getHeight();
}
- // Scale to fit width only - let the widget preview be clipped in the
- // vertical dimension
- float scale = 1f;
if (preScaledWidthOut != null) {
preScaledWidthOut[0] = previewWidth;
}
- if (previewWidth > maxPreviewWidth) {
- scale = maxPreviewWidth / (float) (previewWidth);
- }
+ // Scale to fit width only - let the widget preview be clipped in the
+ // vertical dimension
+ final float scale = previewWidth > maxPreviewWidth
+ ? (maxPreviewWidth / (float) (previewWidth)) : 1f;
if (scale != 1f) {
previewWidth = Math.max((int) (scale * previewWidth), 1);
previewHeight = Math.max((int) (scale * previewHeight), 1);
}
- final Canvas c = new Canvas();
- if (preview == null) {
- // If no bitmap was provided, then allocate a new one with the right size.
- preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
- c.setBitmap(preview);
- } else {
- // If a bitmap was passed in, attempt to reconfigure the bitmap to the same dimensions
- // as the preview.
- try {
- preview.reconfigure(previewWidth, previewHeight, preview.getConfig());
- } catch (IllegalArgumentException e) {
- // This occurs if the preview can't be reconfigured for any reason. In this case,
- // allocate a new bitmap with the right size.
- preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
- }
+ final int previewWidthF = previewWidth;
+ final int previewHeightF = previewHeight;
+ final Drawable drawableF = drawable;
- c.setBitmap(preview);
- c.drawColor(0, PorterDuff.Mode.CLEAR);
- }
-
- // Draw the scaled preview into the final bitmap
- if (widgetPreviewExists) {
- drawable.setBounds(0, 0, previewWidth, previewHeight);
- drawable.draw(c);
- } else {
- RectF boxRect;
-
- // Draw horizontal and vertical lines to represent individual columns.
- final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- if (Utilities.ATLEAST_S) {
- boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
- previewWidth, /* bottom= */ previewHeight);
-
- p.setStyle(Paint.Style.FILL);
- p.setColor(Color.WHITE);
- float roundedCorner = mContext.getResources().getDimension(
- android.R.dimen.system_app_widget_background_radius);
- c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
+ return BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight, c -> {
+ // Draw the scaled preview into the final bitmap
+ if (widgetPreviewExists) {
+ drawableF.setBounds(0, 0, previewWidthF, previewHeightF);
+ drawableF.draw(c);
} else {
- boxRect = drawBoxWithShadow(c, previewWidth, previewHeight);
- }
+ RectF boxRect;
- p.setStyle(Paint.Style.STROKE);
- p.setStrokeWidth(mContext.getResources()
- .getDimension(R.dimen.widget_preview_cell_divider_width));
- p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ // Draw horizontal and vertical lines to represent individual columns.
+ final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
- float t = boxRect.left;
- float tileSize = boxRect.width() / spanX;
- for (int i = 1; i < spanX; i++) {
- t += tileSize;
- c.drawLine(t, 0, t, previewHeight, p);
- }
+ if (Utilities.ATLEAST_S) {
+ boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
+ previewWidthF, /* bottom= */ previewHeightF);
- t = boxRect.top;
- tileSize = boxRect.height() / spanY;
- for (int i = 1; i < spanY; i++) {
- t += tileSize;
- c.drawLine(0, t, previewWidth, t, p);
- }
-
- // Draw icon in the center.
- try {
- Drawable icon =
- mIconCache.getFullResIcon(info.provider.getPackageName(), info.icon);
- if (icon != null) {
- int appIconSize = launcher.getDeviceProfile().iconSizePx;
- int iconSize = (int) Math.min(appIconSize * scale,
- Math.min(boxRect.width(), boxRect.height()));
-
- icon = mutateOnMainThread(icon);
- int hoffset = (previewWidth - iconSize) / 2;
- int yoffset = (previewHeight - iconSize) / 2;
- icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
- icon.draw(c);
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(Color.WHITE);
+ float roundedCorner = mContext.getResources().getDimension(
+ android.R.dimen.system_app_widget_background_radius);
+ c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
+ } else {
+ boxRect = drawBoxWithShadow(c, previewWidthF, previewHeightF);
}
- } catch (Resources.NotFoundException e) {
- savePreviewImage = false;
+
+ p.setStyle(Paint.Style.STROKE);
+ p.setStrokeWidth(mContext.getResources()
+ .getDimension(R.dimen.widget_preview_cell_divider_width));
+ p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ float t = boxRect.left;
+ float tileSize = boxRect.width() / spanX;
+ for (int i = 1; i < spanX; i++) {
+ t += tileSize;
+ c.drawLine(t, 0, t, previewHeightF, p);
+ }
+
+ t = boxRect.top;
+ tileSize = boxRect.height() / spanY;
+ for (int i = 1; i < spanY; i++) {
+ t += tileSize;
+ c.drawLine(0, t, previewWidthF, t, p);
+ }
+
+ // Draw icon in the center.
+ try {
+ Drawable icon = LauncherAppState.getInstance(mContext).getIconCache()
+ .getFullResIcon(info.provider.getPackageName(), info.icon);
+ if (icon != null) {
+ int appIconSize = mContext.getDeviceProfile().iconSizePx;
+ int iconSize = (int) Math.min(appIconSize * scale,
+ Math.min(boxRect.width(), boxRect.height()));
+
+ icon = mutateOnMainThread(icon);
+ int hoffset = (previewWidthF - iconSize) / 2;
+ int yoffset = (previewHeightF - iconSize) / 2;
+ icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
+ icon.draw(c);
+ }
+ } catch (Resources.NotFoundException e) {
+ }
}
- c.setBitmap(null);
- }
- return new Pair<>(preview, savePreviewImage);
+ });
}
private RectF drawBoxWithShadow(Canvas c, int width, int height) {
@@ -537,42 +246,29 @@
return builder.bounds;
}
- private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info,
- int maxWidth, int maxHeight, Bitmap preview) {
- int iconSize = launcher.getDeviceProfile().allAppsIconSizePx;
- int padding = launcher.getResources()
+ private Bitmap generateShortcutPreview(
+ ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) {
+ int iconSize = mContext.getDeviceProfile().allAppsIconSizePx;
+ int padding = mContext.getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
int size = iconSize + 2 * padding;
if (maxHeight < size || maxWidth < size) {
throw new RuntimeException("Max size is too small for preview");
}
- final Canvas c = new Canvas();
- if (preview == null || preview.getWidth() < size || preview.getHeight() < size) {
- preview = Bitmap.createBitmap(size, size, Config.ARGB_8888);
- c.setBitmap(preview);
- } else {
- if (preview.getWidth() > size || preview.getHeight() > size) {
- preview.reconfigure(size, size, preview.getConfig());
- }
+ return BitmapRenderer.createHardwareBitmap(size, size, c -> {
+ drawBoxWithShadow(c, size, size);
- // Reusing bitmap. Clear it.
- c.setBitmap(preview);
- c.drawColor(0, PorterDuff.Mode.CLEAR);
- }
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ Drawable icon = li.createBadgedIconBitmap(
+ mutateOnMainThread(info.getFullResIcon(
+ LauncherAppState.getInstance(mContext).getIconCache())),
+ Process.myUserHandle(), 0).newIcon(mContext);
+ li.recycle();
- drawBoxWithShadow(c, size, size);
-
- LauncherIcons li = LauncherIcons.obtain(mContext);
- Drawable icon = li.createBadgedIconBitmap(
- mutateOnMainThread(info.getFullResIcon(mIconCache)),
- Process.myUserHandle(), 0).newIcon(launcher);
- li.recycle();
-
- icon.setBounds(padding, padding, padding + iconSize, padding + iconSize);
- icon.draw(c);
- c.setBitmap(null);
- return preview;
+ icon.setBounds(padding, padding, padding + iconSize, padding + iconSize);
+ icon.draw(c);
+ });
}
private Drawable mutateOnMainThread(final Drawable drawable) {
@@ -585,206 +281,4 @@
throw new RuntimeException(e);
}
}
-
- /**
- * @return an array of containing versionCode and lastUpdatedTime for the package.
- */
- @Thunk long[] getPackageVersion(String packageName) {
- synchronized (mPackageVersions) {
- long[] versions = mPackageVersions.get(packageName);
- if (versions == null) {
- versions = new long[2];
- try {
- PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES);
- versions[0] = info.versionCode;
- versions[1] = info.lastUpdateTime;
- } catch (NameNotFoundException e) {
- Log.e(TAG, "PackageInfo not found", e);
- }
- mPackageVersions.put(packageName, versions);
- }
- return versions;
- }
- }
-
- private class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
- implements CancellationSignal.OnCancelListener {
- @Thunk final WidgetCacheKey mKey;
- private final WidgetItem mInfo;
- private final int mPreviewHeight;
- private final int mPreviewWidth;
- private final WidgetPreviewLoadedCallback mCallback;
- private final BaseActivity mActivity;
- @Thunk long[] mVersions;
- @Thunk Bitmap mBitmapToRecycle;
-
- @Nullable private Bitmap mUnusedPreviewBitmap;
- private boolean mSaveToDB = false;
-
- PreviewLoadTask(BaseActivity activity, WidgetCacheKey key, WidgetItem info,
- int previewWidth, int previewHeight, WidgetPreviewLoadedCallback callback) {
- mActivity = activity;
- mKey = key;
- mInfo = info;
- mPreviewHeight = previewHeight;
- mPreviewWidth = previewWidth;
- mCallback = callback;
- if (DEBUG) {
- Log.d(TAG, String.format("%s, %s, %d, %d",
- mKey, mInfo, mPreviewHeight, mPreviewWidth));
- }
- }
-
- @Override
- protected Bitmap doInBackground(Void... params) {
- Bitmap unusedBitmap = null;
-
- // If already cancelled before this gets to run in the background, then return early
- if (isCancelled()) {
- return null;
- }
- synchronized (mUnusedBitmaps) {
- // Check if we can re-use a bitmap
- for (Bitmap candidate : mUnusedBitmaps) {
- if (candidate != null && candidate.isMutable()
- && candidate.getWidth() == mPreviewWidth
- && candidate.getHeight() == mPreviewHeight) {
- unusedBitmap = candidate;
- mUnusedBitmaps.remove(unusedBitmap);
- break;
- }
- }
- }
-
- // creating a bitmap is expensive. Do not do this inside synchronized block.
- if (unusedBitmap == null) {
- unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888);
- }
- // If cancelled now, don't bother reading the preview from the DB
- if (isCancelled()) {
- return unusedBitmap;
- }
- Bitmap preview = readFromDb(mKey, unusedBitmap, this);
- // Only consider generating the preview if we have not cancelled the task already
- if (!isCancelled() && preview == null) {
- // Fetch the version info before we generate the preview, so that, in-case the
- // app was updated while we are generating the preview, we use the old version info,
- // which would gets re-written next time.
- boolean persistable = mInfo.activityInfo == null
- || mInfo.activityInfo.isPersistable();
- mVersions = persistable ? getPackageVersion(mKey.componentName.getPackageName())
- : null;
-
- // it's not in the db... we need to generate it
- Pair<Bitmap, Boolean> pair = generatePreview(mActivity, mInfo, unusedBitmap,
- mPreviewWidth, mPreviewHeight);
- preview = pair.first;
-
- if (preview != unusedBitmap) {
- mUnusedPreviewBitmap = unusedBitmap;
- }
-
- this.mSaveToDB = pair.second;
- }
- return preview;
- }
-
- @Override
- protected void onPostExecute(final Bitmap preview) {
- mCallback.onPreviewLoaded(preview);
-
- // Write the generated preview to the DB in the worker thread
- if (mVersions != null) {
- MODEL_EXECUTOR.post(new Runnable() {
- @Override
- public void run() {
- if (mUnusedPreviewBitmap != null) {
- // If we didn't end up using the bitmap, it can be added back into the
- // recycled set.
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(mUnusedPreviewBitmap);
- }
- }
-
- if (!isCancelled() && mSaveToDB) {
- // If we are still using this preview, then write it to the DB and then
- // let the normal clear mechanism recycle the bitmap
- writeToDb(mKey, mVersions, preview);
- mBitmapToRecycle = preview;
- } else {
- // If we've already cancelled, then skip writing the bitmap to the DB
- // and manually add the bitmap back to the recycled set
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(preview);
- }
- }
- }
- });
- } else {
- // If we don't need to write to disk, then ensure the preview gets recycled by
- // the normal clear mechanism
- mBitmapToRecycle = preview;
- }
- }
-
- @Override
- protected void onCancelled(final Bitmap preview) {
- // If we've cancelled while the task is running, then can return the bitmap to the
- // recycled set immediately. Otherwise, it will be recycled after the preview is written
- // to disk.
- if (preview != null) {
- MODEL_EXECUTOR.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(preview);
- }
- }
- });
- }
- }
-
- @Override
- public void onCancel() {
- cancel(true);
-
- // This only handles the case where the PreviewLoadTask is cancelled after the task has
- // successfully completed (including having written to disk when necessary). In the
- // other cases where it is cancelled while the task is running, it will be cleaned up
- // in the tasks's onCancelled() call, and if cancelled while the task is writing to
- // disk, it will be cancelled in the task's onPostExecute() call.
- if (mBitmapToRecycle != null) {
- MODEL_EXECUTOR.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(mBitmapToRecycle);
- }
- mBitmapToRecycle = null;
- }
- });
- }
- }
- }
-
- private static final class WidgetCacheKey extends ComponentKey {
-
- @Thunk final String mSize;
-
- WidgetCacheKey(ComponentName componentName, UserHandle user, String size) {
- super(componentName, user);
- this.mSize = size;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode() ^ mSize.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- return super.equals(o) && ((WidgetCacheKey) o).mSize.equals(mSize);
- }
- }
}
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 991910d..2347d28 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -140,10 +140,9 @@
.addDragListener(new AppWidgetHostViewDragListener(launcher));
}
if (preview == null && mAppWidgetHostViewPreview == null) {
- Drawable p = new FastBitmapDrawable(
- app.getWidgetCache().generateWidgetPreview(launcher,
- createWidgetInfo.info, maxWidth, null,
- previewSizeBeforeScale).first);
+ Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher)
+ .generateWidgetPreview(
+ createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index bd444db..423c66a 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -26,14 +26,12 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.os.CancellationSignal;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -42,6 +40,7 @@
import android.widget.RemoteViews;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
@@ -51,9 +50,13 @@
import com.android.launcher3.R;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.RoundDrawableWrapper;
+import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.util.WidgetSizes;
+import java.util.function.Consumer;
+
/**
* Represents the individual cell of the widget inside the widget tray. The preview is drawn
* horizontally centered, and scaled down if needed.
@@ -63,7 +66,7 @@
* transition from the view to drag view, so when adding padding support, DnD would need to
* consider the appropriate scaling factor.
*/
-public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
+public class WidgetCell extends LinearLayout {
private static final String TAG = "WidgetCell";
private static final boolean DEBUG = false;
@@ -115,14 +118,11 @@
protected WidgetItem mItem;
- private WidgetPreviewLoader mWidgetPreviewLoader;
+ private final DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- protected CancellationSignal mActiveRequest;
+ protected HandlerRunnable mActiveRequest;
private boolean mAnimatePreview = true;
- private boolean mApplyBitmapDeferred = false;
- private Drawable mDeferredDrawable;
-
protected final BaseActivity mActivity;
private final CheckLongPressHelper mLongPressHelper;
private final float mEnforcedCornerRadius;
@@ -144,6 +144,7 @@
super(context, attrs, defStyle);
mActivity = BaseActivity.fromContext(context);
+ mWidgetPreviewLoader = new DatabaseWidgetPreviewLoader(mActivity);
mLongPressHelper = new CheckLongPressHelper(this);
mLongPressHelper.setLongPressTimeoutFactor(1);
@@ -218,7 +219,36 @@
this.mSourceContainer = sourceContainer;
}
- public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
+ /**
+ * Applies the item to this view
+ */
+ public void applyFromCellItem(WidgetItem item) {
+ applyFromCellItem(item, 1f);
+ }
+
+ /**
+ * Applies the item to this view
+ */
+ public void applyFromCellItem(WidgetItem item, float previewScale) {
+ applyFromCellItem(item, previewScale, this::applyPreview, null);
+ }
+
+ /**
+ * Applies the item to this view
+ * @param item item to apply
+ * @param previewScale factor to scale the preview
+ * @param callback callback when preview is loaded in case the preview is being loaded or cached
+ * @param cachedPreview previously cached preview bitmap is present
+ */
+ public void applyFromCellItem(WidgetItem item, float previewScale,
+ @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
+ // setPreviewSize
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, item);
+ mTargetPreviewWidth = widgetSize.getWidth();
+ mTargetPreviewHeight = widgetSize.getHeight();
+ mPreviewContainerScale = previewScale;
+
applyPreviewOnAppWidgetHostView(item);
Context context = getContext();
@@ -240,14 +270,14 @@
}
}
- mWidgetPreviewLoader = loader;
if (item.activityInfo != null) {
setTag(new PendingAddShortcutInfo(item.activityInfo));
} else {
setTag(new PendingAddWidgetInfo(item.widgetInfo, mSourceContainer));
}
- }
+ ensurePreviewWithCallback(callback, cachedPreview);
+ }
private void applyPreviewOnAppWidgetHostView(WidgetItem item) {
if (mRemoteViewsPreview != null) {
@@ -294,37 +324,15 @@
return mAppWidgetHostViewPreview;
}
- /**
- * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
- * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
- * ready.
- * This prevents invalidates while the animation is running.
- */
- public void setApplyBitmapDeferred(boolean isDeferred) {
- if (mApplyBitmapDeferred != isDeferred) {
- mApplyBitmapDeferred = isDeferred;
- if (!mApplyBitmapDeferred && mDeferredDrawable != null) {
- applyPreview(mDeferredDrawable);
- mDeferredDrawable = null;
- }
- }
- }
-
public void setAnimatePreview(boolean shouldAnimate) {
mAnimatePreview = shouldAnimate;
}
- public void applyPreview(Bitmap bitmap) {
- FastBitmapDrawable drawable = new FastBitmapDrawable(bitmap);
- applyPreview(new RoundDrawableWrapper(drawable, mEnforcedCornerRadius));
- }
+ private void applyPreview(Bitmap bitmap) {
+ if (bitmap != null) {
+ Drawable drawable = new RoundDrawableWrapper(
+ new FastBitmapDrawable(bitmap), mEnforcedCornerRadius);
- private void applyPreview(Drawable drawable) {
- if (mApplyBitmapDeferred) {
- mDeferredDrawable = drawable;
- return;
- }
- if (drawable != null) {
// Scale down the preview size if it's wider than the cell.
float scale = 1f;
if (mTargetPreviewWidth > 0) {
@@ -349,6 +357,10 @@
} else {
mWidgetImageContainer.setAlpha(1f);
}
+ if (mActiveRequest != null) {
+ mActiveRequest.cancel();
+ mActiveRequest = null;
+ }
}
private void setContainerSize(int width, int height) {
@@ -358,7 +370,13 @@
mWidgetImageContainer.setLayoutParams(layoutParams);
}
- public void ensurePreview() {
+ /**
+ * Ensures that the preview is already loaded or being loaded. If the preview is not loaded,
+ * it applies the provided cachedPreview. If that is null, it starts a loader and notifies the
+ * callback on successful load.
+ */
+ private void ensurePreviewWithCallback(Consumer<Bitmap> callback,
+ @Nullable Bitmap cachedPreview) {
if (mAppWidgetHostViewPreview != null) {
int containerWidth = (int) (mTargetPreviewWidth * mPreviewContainerScale);
int containerHeight = (int) (mTargetPreviewHeight * mPreviewContainerScale);
@@ -382,38 +400,18 @@
mAppWidgetHostViewPreview.setLayoutParams(params);
mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
mWidgetImage.setVisibility(View.GONE);
- applyPreview((Drawable) null);
+ applyPreview(null);
+ return;
+ }
+ if (cachedPreview != null) {
+ applyPreview(cachedPreview);
return;
}
if (mActiveRequest != null) {
return;
}
mActiveRequest = mWidgetPreviewLoader.loadPreview(
- BaseActivity.fromContext(getContext()), mItem,
- new Size(mTargetPreviewWidth, mTargetPreviewHeight),
- this::applyPreview);
- }
-
- /** Sets the widget preview image size in number of cells. */
- public Size setPreviewSize(WidgetItem widgetItem) {
- return setPreviewSize(widgetItem, 1f);
- }
-
- /** Sets the widget preview image size, in number of cells, and preview scale. */
- public Size setPreviewSize(WidgetItem widgetItem, float previewScale) {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, widgetItem);
- mTargetPreviewWidth = widgetSize.getWidth();
- mTargetPreviewHeight = widgetSize.getHeight();
- mPreviewContainerScale = previewScale;
- return widgetSize;
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- removeOnLayoutChangeListener(this);
- ensurePreview();
+ mItem, new Size(mTargetPreviewWidth, mTargetPreviewHeight), callback);
}
@Override
@@ -429,17 +427,6 @@
mLongPressHelper.cancelLongPress();
}
- /**
- * Helper method to get the string info of the tag.
- */
- private String getTagToString() {
- if (getTag() instanceof PendingAddWidgetInfo ||
- getTag() instanceof PendingAddShortcutInfo) {
- return getTag().toString();
- }
- return "";
- }
-
private static NavigableAppWidgetHostView createAppWidgetHostView(Context context) {
return new NavigableAppWidgetHostView(context) {
@Override
@@ -450,12 +437,7 @@
}
private static boolean isLauncherContext(Context context) {
- try {
- Launcher.getLauncher(context);
- return true;
- } catch (Exception e) {
- return false;
- }
+ return ActivityContext.lookupContext(context) instanceof Launcher;
}
@Override
diff --git a/src/com/android/launcher3/widget/WidgetPreviewLoader.java b/src/com/android/launcher3/widget/WidgetPreviewLoader.java
deleted file mode 100644
index ff5c82f..0000000
--- a/src/com/android/launcher3/widget/WidgetPreviewLoader.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.model.WidgetItem;
-
-/** Asynchronous loader of preview bitmaps for {@link WidgetItem}s. */
-public interface WidgetPreviewLoader {
- /**
- * Loads a widget preview and calls back to {@code callback} when complete.
- *
- * @return a {@link CancellationSignal} which can be used to cancel the request.
- */
- @NonNull
- @UiThread
- CancellationSignal loadPreview(
- @NonNull BaseActivity activity,
- @NonNull WidgetItem item,
- @NonNull Size previewSize,
- @NonNull WidgetPreviewLoadedCallback callback);
-
- /** Callback class for requests to {@link WidgetPreviewLoader}. */
- interface WidgetPreviewLoadedCallback {
- void onPreviewLoaded(@NonNull Bitmap preview);
- }
-}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 6beff3a..bb4638a 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -37,7 +37,6 @@
import android.widget.TextView;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.WidgetItem;
@@ -199,11 +198,7 @@
tableRow.setGravity(Gravity.TOP);
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
- widget.setPreviewSize(widgetItem);
- widget.applyFromCellItem(widgetItem, LauncherAppState.getInstance(mActivityContext)
- .getWidgetCache());
- widget.ensurePreview();
- widget.setVisibility(View.VISIBLE);
+ widget.applyFromCellItem(widgetItem);
});
widgetsTable.addView(tableRow);
});
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 9dbfa87..09f0299 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -687,7 +687,7 @@
.findFirst()
.orElse(null);
if (viewHolderForTip != null) {
- return ((ViewGroup) viewHolderForTip.mTableContainer.getChildAt(0)).getChildAt(0);
+ return ((ViewGroup) viewHolderForTip.tableContainer.getChildAt(0)).getChildAt(0);
}
return null;
@@ -745,7 +745,6 @@
mWidgetsListAdapter = new WidgetsListAdapter(
context,
LayoutInflater.from(context),
- apps.getWidgetCache(),
apps.getIconCache(),
this::getEmptySpaceHeight,
/* iconClickListener= */ WidgetsFullSheet.this,
@@ -784,7 +783,6 @@
if (mAdapterType == PRIMARY || mAdapterType == WORK) {
mWidgetsRecyclerView.addOnAttachStateChangeListener(mBindScrollbarInSearchMode);
}
- mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 1ad1f7a..de0d8b8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -24,14 +24,12 @@
import android.graphics.Rect;
import android.os.Process;
import android.util.Log;
-import android.util.Size;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import android.widget.TableRow;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -41,29 +39,22 @@
import androidx.recyclerview.widget.RecyclerView.LayoutParams;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.LabelComparator;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
-import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.util.WidgetSizes;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -94,12 +85,9 @@
private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
- private final Context mContext;
private final Launcher mLauncher;
- private final CachingWidgetPreviewLoader mCachingPreviewLoader;
private final WidgetsDiffReporter mDiffReporter;
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
- private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
private final WidgetListBaseRowEntryComparator mRowComparator =
new WidgetListBaseRowEntryComparator();
@@ -115,26 +103,21 @@
@Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
@Nullable private RecyclerView mRecyclerView;
@Nullable private PackageUserKey mPendingClickHeader;
- private final int mShortcutPreviewPadding;
private final int mSpacingBetweenEntries;
private int mMaxSpanSize = 4;
- private final WidgetPreviewLoadedCallback mPreviewLoadedCallback =
- ignored -> updateVisibleEntries();
-
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
- DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
- IntSupplier emptySpaceHeightProvider,
+ IconCache iconCache, IntSupplier emptySpaceHeightProvider,
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
- mContext = context;
mLauncher = Launcher.getLauncher(context);
- mCachingPreviewLoader = new CachingWidgetPreviewLoader(widgetPreviewLoader);
mDiffReporter = new WidgetsDiffReporter(iconCache, this);
WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
- mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
- layoutInflater, iconClickListener, iconLongClickListener,
- mCachingPreviewLoader, listDrawableFactory);
- mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
+
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_LIST,
+ new WidgetsListTableViewHolderBinder(
+ layoutInflater, iconClickListener, iconLongClickListener,
+ listDrawableFactory));
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_HEADER,
new WidgetsListHeaderViewHolderBinder(
@@ -150,9 +133,6 @@
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_SPACE,
new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
- mShortcutPreviewPadding =
- 2 * context.getResources()
- .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
mSpacingBetweenEntries =
context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
}
@@ -186,28 +166,6 @@
mFilter = filter;
}
- /**
- * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}.
- *
- * @see WidgetCell#setApplyBitmapDeferred(boolean)
- */
- public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
- mWidgetsListTableViewHolderBinder.setApplyBitmapDeferred(isDeferred);
-
- for (int i = rv.getChildCount() - 1; i >= 0; i--) {
- ViewHolder viewHolder = rv.getChildViewHolder(rv.getChildAt(i));
- if (viewHolder.getItemViewType() == VIEW_TYPE_WIDGETS_LIST) {
- WidgetsRowViewHolder holder = (WidgetsRowViewHolder) viewHolder;
- for (int j = holder.mTableContainer.getChildCount() - 1; j >= 0; j--) {
- TableRow row = (TableRow) holder.mTableContainer.getChildAt(j);
- for (int k = row.getChildCount() - 1; k >= 0; k--) {
- ((WidgetCell) row.getChildAt(k)).setApplyBitmapDeferred(isDeferred);
- }
- }
- }
- }
- }
-
@Override
public int getItemCount() {
return mVisibleEntries.size();
@@ -233,7 +191,6 @@
/** Updates the widget list based on {@code tempEntries}. */
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
- mCachingPreviewLoader.clearAll();
mAllEntries.clear();
mAllEntries.add(new WidgetListSpaceEntry());
tempEntries.stream().sorted(mRowComparator).forEach(mAllEntries::add);
@@ -247,15 +204,10 @@
public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
// Forget the expanded package every time widget list is refreshed in search mode.
mWidgetsContentVisiblePackageUserKey = null;
- cancelLoadingPreviews();
setWidgets(searchResults);
}
private void updateVisibleEntries() {
- // If not all previews are ready, then defer this update and try again after the preview
- // loads.
- if (!ensureAllPreviewsReady()) return;
-
// Get the current top of the header with the matching key before adjusting the visible
// entries.
OptionalInt previousPositionForPackageUserKey =
@@ -293,54 +245,6 @@
}
}
- /**
- * Checks that all preview images are loaded and starts loading for those that aren't ready.
- *
- * @return true if all previews are ready and the data can be updated, false otherwise.
- */
- private boolean ensureAllPreviewsReady() {
- boolean allReady = true;
- BaseActivity activity = BaseActivity.fromContext(mContext);
- for (WidgetsListBaseEntry entry : mAllEntries) {
- if (!(entry instanceof WidgetsListContentEntry)) continue;
-
- WidgetsListContentEntry contentEntry = (WidgetsListContentEntry) entry;
- if (!matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
- // If the entry isn't visible, clear any loaded previews.
- mCachingPreviewLoader.clearPreviews(contentEntry.mWidgets);
- continue;
- }
-
- for (int i = 0; i < entry.mWidgets.size(); i++) {
- WidgetItem widgetItem = entry.mWidgets.get(i);
- DeviceProfile deviceProfile = activity.getDeviceProfile();
- Size widgetSize = WidgetSizes.getWidgetItemSizePx(mContext, deviceProfile,
- widgetItem);
- if (widgetItem.isShortcut()) {
- widgetSize =
- new Size(
- widgetSize.getWidth() + mShortcutPreviewPadding,
- widgetSize.getHeight() + mShortcutPreviewPadding);
- }
-
- if (widgetItem.hasPreviewLayout()
- || mCachingPreviewLoader.isPreviewLoaded(widgetItem, widgetSize)) {
- // The widget is ready if it can be rendered with a preview layout or if its
- // preview bitmap is in the cache.
- continue;
- }
-
- // If we've reached this point, we should load the preview for the widget.
- allReady = false;
- mCachingPreviewLoader.loadPreview(
- activity,
- widgetItem,
- widgetSize,
- mPreviewLoadedCallback);
- }
- }
- return allReady;
- }
/** Returns whether {@code entry} matches {@code key}. */
private static boolean isHeaderForPackageUserKey(
@@ -361,13 +265,17 @@
public void resetExpandedHeader() {
if (mWidgetsContentVisiblePackageUserKey != null) {
mWidgetsContentVisiblePackageUserKey = null;
- cancelLoadingPreviews();
updateVisibleEntries();
}
}
@Override
- public void onBindViewHolder(ViewHolder holder, int pos) {
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ onBindViewHolder(holder, position, Collections.EMPTY_LIST);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int pos, List<Object> payloads) {
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
@@ -376,7 +284,7 @@
if (pos == (getItemCount() - 1)) {
listPos |= POSITION_LAST;
}
- viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos);
+ viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos, payloads);
holder.itemView.setTag(R.id.tag_widget_entry, entry);
}
@@ -430,8 +338,6 @@
// Ignore invalid clicks, such as collapsing a package that isn't currently expanded.
if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return;
- cancelLoadingPreviews();
-
if (showWidgets) {
mWidgetsContentVisiblePackageUserKey = packageUserKey;
mLauncher.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED);
@@ -446,16 +352,6 @@
updateVisibleEntries();
}
- private void cancelLoadingPreviews() {
- mCachingPreviewLoader.clearAll();
- }
-
- /** Returns the position of the currently expanded header, or empty if it's not present. */
- public OptionalInt getSelectedHeaderPosition() {
- if (mWidgetsContentVisiblePackageUserKey == null) return OptionalInt.empty();
- return getPositionForPackageUserKey(mWidgetsContentVisiblePackageUserKey);
- }
-
/**
* Returns the position of {@code key} in {@link #mVisibleEntries}, or empty if it's not
* present.
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index 00750bd..fadb637 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -23,6 +23,8 @@
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import java.util.List;
+
/**
* Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
*/
@@ -50,7 +52,7 @@
@Override
public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
- @ListPosition int position) {
+ @ListPosition int position, List<Object> payloads) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
index 1e2a3bf..bff43c1 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -24,6 +24,8 @@
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+import java.util.List;
+
/**
* Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
*/
@@ -51,7 +53,7 @@
@Override
public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
- WidgetsListSearchHeaderEntry data, @ListPosition int position) {
+ WidgetsListSearchHeaderEntry data, @ListPosition int position, List<Object> payloads) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 804b0ae..feeb0fe 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -20,7 +20,7 @@
import android.graphics.Bitmap;
import android.util.Log;
-import android.util.Size;
+import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,7 +33,6 @@
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.recyclerview.ViewHolderBinder;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.util.WidgetsTableUtils;
@@ -53,31 +52,18 @@
private final OnClickListener mIconClickListener;
private final OnLongClickListener mIconLongClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final CachingWidgetPreviewLoader mWidgetPreviewLoader;
- private boolean mApplyBitmapDeferred = false;
public WidgetsListTableViewHolderBinder(
LayoutInflater layoutInflater,
OnClickListener iconClickListener,
OnLongClickListener iconLongClickListener,
- CachingWidgetPreviewLoader widgetPreviewLoader,
WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- mWidgetPreviewLoader = widgetPreviewLoader;
mListDrawableFactory = listDrawableFactory;
}
- /**
- * Defers applying bitmap on all the {@link WidgetCell} at
- * {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry, int)} if
- * {@code applyBitmapDeferred} is {@code true}.
- */
- public void setApplyBitmapDeferred(boolean applyBitmapDeferred) {
- mApplyBitmapDeferred = applyBitmapDeferred;
- }
-
@Override
public WidgetsRowViewHolder newViewHolder(ViewGroup parent) {
if (DEBUG) {
@@ -87,25 +73,30 @@
WidgetsRowViewHolder viewHolder =
new WidgetsRowViewHolder(mLayoutInflater.inflate(
R.layout.widgets_table_container, parent, false));
- viewHolder.mTableContainer.setBackgroundDrawable(
+ viewHolder.tableContainer.setBackgroundDrawable(
mListDrawableFactory.createContentBackgroundDrawable());
return viewHolder;
}
@Override
public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
- @ListPosition int position) {
- WidgetsListTableView table = holder.mTableContainer;
+ @ListPosition int position, List<Object> payloads) {
+ for (Object payload : payloads) {
+ Pair<WidgetItem, Bitmap> pair = (Pair) payload;
+ holder.previewCache.put(pair.first, pair.second);
+ }
+
+ WidgetsListTableView table = holder.tableContainer;
if (DEBUG) {
Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
entry.mWidgets.size(), table.getChildCount()));
}
table.setListDrawableState(((position & POSITION_LAST) != 0) ? LAST : MIDDLE);
-
List<ArrayList<WidgetItem>> widgetItemsTable =
WidgetsTableUtils.groupWidgetItemsIntoTable(
entry.mWidgets, entry.getMaxSpanSizeInCells());
recycleTableBeforeBinding(table, widgetItemsTable);
+
// Bind the widget items.
for (int i = 0; i < widgetItemsTable.size(); i++) {
List<WidgetItem> widgetItemsPerRow = widgetItemsTable.get(i);
@@ -115,16 +106,14 @@
WidgetCell widget = (WidgetCell) row.getChildAt(j);
widget.clear();
WidgetItem widgetItem = widgetItemsPerRow.get(j);
- Size previewSize = widget.setPreviewSize(widgetItem);
- widget.applyFromCellItem(widgetItem, mWidgetPreviewLoader);
- widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
- Bitmap preview = mWidgetPreviewLoader.getPreview(widgetItem, previewSize);
- if (preview == null) {
- widget.ensurePreview();
- } else {
- widget.applyPreview(preview);
- }
widget.setVisibility(View.VISIBLE);
+
+ // When preview loads, notify adapter to rebind the item and possibly animate
+ widget.applyFromCellItem(widgetItem, 1f,
+ bitmap -> holder.getBindingAdapter().notifyItemChanged(
+ holder.getBindingAdapterPosition(),
+ Pair.create(widgetItem, bitmap)),
+ holder.previewCache.get(widgetItem));
}
}
}
@@ -165,6 +154,7 @@
View preview = widget.findViewById(R.id.widget_preview_container);
preview.setOnClickListener(mIconClickListener);
preview.setOnLongClickListener(mIconLongClickListener);
+ widget.setAnimatePreview(false);
tableRow.addView(widget);
}
}
@@ -173,9 +163,10 @@
@Override
public void unbindViewHolder(WidgetsRowViewHolder holder) {
- int numOfRows = holder.mTableContainer.getChildCount();
+ int numOfRows = holder.tableContainer.getChildCount();
+ holder.previewCache.clear();
for (int i = 0; i < numOfRows; i++) {
- TableRow tableRow = (TableRow) holder.mTableContainer.getChildAt(i);
+ TableRow tableRow = (TableRow) holder.tableContainer.getChildAt(i);
int numOfCols = tableRow.getChildCount();
for (int j = 0; j < numOfCols; j++) {
WidgetCell widget = (WidgetCell) tableRow.getChildAt(j);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 60dfebe..c986007 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -32,7 +32,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.WidgetCell;
@@ -109,10 +108,7 @@
for (WidgetItem widgetItem : widgetItems) {
WidgetCell widgetCell = addItemCell(tableRow);
- widgetCell.setPreviewSize(widgetItem, data.mPreviewScale);
- widgetCell.applyFromCellItem(widgetItem,
- LauncherAppState.getInstance(getContext()).getWidgetCache());
- widgetCell.ensurePreview();
+ widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
}
addView(tableRow);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
index 618e2cb..fe2d84b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
@@ -15,20 +15,26 @@
*/
package com.android.launcher3.widget.picker;
+import android.graphics.Bitmap;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+
+import java.util.HashMap;
+import java.util.Map;
/** A {@link ViewHolder} for showing widgets of an app in the full widget picker. */
public final class WidgetsRowViewHolder extends ViewHolder {
- public final WidgetsListTableView mTableContainer;
+ public final WidgetsListTableView tableContainer;
+ public final Map<WidgetItem, Bitmap> previewCache = new HashMap<>();
public WidgetsRowViewHolder(View v) {
super(v);
- mTableContainer = v.findViewById(R.id.widgets_table);
+ tableContainer = v.findViewById(R.id.widgets_table);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
index f33c2fa..1aa5753 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
@@ -27,6 +27,7 @@
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.widget.model.WidgetListSpaceEntry;
+import java.util.List;
import java.util.function.IntSupplier;
/**
@@ -47,7 +48,8 @@
}
@Override
- public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data, int position) {
+ public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data,
+ @ListPosition int position, List<Object> payloads) {
((EmptySpaceView) holder.itemView).setFixedHeight(mEmptySpaceHeightProvider.getAsInt());
}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 631067b..12e9e1e 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -150,7 +150,6 @@
}
}
- app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser);
return updatedItems;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 2ca40d8..23aaa25 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -251,8 +251,19 @@
} else {
try {
final int userId = ContextUtils.getUserId(getContext());
+ final String launcherPidCommand = "pidof " + pi.packageName;
+ final String initialPid = mDevice.executeShellCommand(launcherPidCommand)
+ .replaceAll("\\s", "");
mDevice.executeShellCommand(
"pm enable --user " + userId + " " + cn.flattenToString());
+ // Wait for Launcher restart after enabling test provider.
+ for (int i = 0; i < 100; ++i) {
+ final String currentPid = mDevice.executeShellCommand(launcherPidCommand)
+ .replaceAll("\\s", "");
+ if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break;
+ if (i == 99) fail("Launcher didn't restart after enabling test provider");
+ SystemClock.sleep(100);
+ }
} catch (IOException e) {
fail(e.toString());
}
@@ -305,7 +316,7 @@
public boolean isTwoPanels() {
return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS)
- .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
private void setForcePauseTimeout(long timeout) {