Merge "Disable gesture nav while PiP anim is running" into main
diff --git a/go/quickstep/res/values/styles.xml b/go/quickstep/res/values/styles.xml
index c659331..2524c76 100644
--- a/go/quickstep/res/values/styles.xml
+++ b/go/quickstep/res/values/styles.xml
@@ -16,7 +16,7 @@
-->
<resources>
<!-- App themes -->
- <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
+ <style name="LauncherTheme" parent="@style/DynamicColorsBaseLauncherTheme">
<item name="overviewButtonTextColor">@color/go_overview_text_color</item>
<item name="overviewButtonIconColor">@color/go_overview_text_color</item>
<item name="overviewButtonBackgroundColor">@color/go_overview_button_color</item>
diff --git a/quickstep/res/color/all_set_bg_primary.xml b/quickstep/res/color/all_set_bg_primary.xml
index 4cf857d..013de7a 100644
--- a/quickstep/res/color/all_set_bg_primary.xml
+++ b/quickstep/res/color/all_set_bg_primary.xml
@@ -15,5 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="?androidprv:attr/materialColorPrimaryContainer"/>
+ <item android:color="?attr/materialColorPrimaryContainer"/>
</selector>
diff --git a/quickstep/res/color/all_set_bg_tertiary.xml b/quickstep/res/color/all_set_bg_tertiary.xml
index e62b094..b58d61c 100644
--- a/quickstep/res/color/all_set_bg_tertiary.xml
+++ b/quickstep/res/color/all_set_bg_tertiary.xml
@@ -15,5 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="?androidprv:attr/materialColorTertiary"/>
+ <item android:color="?attr/materialColorTertiary"/>
</selector>
diff --git a/quickstep/res/color/bubblebar_drop_target_bg_color.xml b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
index ca37c7f..bae8c4e 100644
--- a/quickstep/res/color/bubblebar_drop_target_bg_color.xml
+++ b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
@@ -15,5 +15,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:alpha="0.35" android:color="?androidprv:attr/materialColorPrimaryContainer" />
+ <item android:alpha="0.35" android:color="?attr/materialColorPrimaryContainer" />
</selector>
\ No newline at end of file
diff --git a/quickstep/res/color/menu_item_hover_state_color.xml b/quickstep/res/color/menu_item_hover_state_color.xml
index 1dd7654..3c68789 100644
--- a/quickstep/res/color/menu_item_hover_state_color.xml
+++ b/quickstep/res/color/menu_item_hover_state_color.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:state_hovered="false" android:color="?androidprv:attr/materialColorSurfaceBright" />
- <item android:state_hovered="true" android:color="?androidprv:attr/materialColorSurfaceVariant" />
+ <item android:state_hovered="false" android:color="?attr/materialColorSurfaceBright" />
+ <item android:state_hovered="true" android:color="?attr/materialColorSurfaceVariant" />
</selector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_bubble_bar_drop_target.xml b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
index 79e4318..f597cb5 100644
--- a/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
+++ b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
@@ -20,5 +20,5 @@
<solid android:color="@color/bubblebar_drop_target_bg_color" />
<stroke
android:width="1dp"
- android:color="?androidprv:attr/materialColorPrimaryContainer" />
+ android:color="?attr/materialColorPrimaryContainer" />
</shape>
diff --git a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
index d722dd7..169e396 100644
--- a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
+++ b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
@@ -22,6 +22,6 @@
<solid android:color="@color/bubblebar_drop_target_bg_color" />
<stroke
android:width="1dp"
- android:color="?androidprv:attr/materialColorPrimaryContainer" />
+ android:color="?attr/materialColorPrimaryContainer" />
</shape>
</inset>
diff --git a/quickstep/res/drawable/bg_floating_desktop_select.xml b/quickstep/res/drawable/bg_floating_desktop_select.xml
index d7df338..6481be4 100644
--- a/quickstep/res/drawable/bg_floating_desktop_select.xml
+++ b/quickstep/res/drawable/bg_floating_desktop_select.xml
@@ -19,5 +19,5 @@
android:shape="rectangle">
<corners android:radius="@dimen/rounded_button_radius" />
- <solid android:color="?androidprv:attr/materialColorPrimaryContainer" />
+ <solid android:color="?attr/materialColorPrimaryContainer" />
</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
index 143761f..0d12274 100644
--- a/quickstep/res/drawable/bg_overview_clear_all_button.xml
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -21,7 +21,7 @@
<shape android:shape="rectangle"
android:tint="?colorButtonNormal">
<corners android:radius="@dimen/recents_clear_all_outline_radius" />
- <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+ <solid android:color="?attr/materialColorSurfaceBright"/>
</shape>
</item>
</ripple>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml b/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
index 20c524d..9e9bb2b 100644
--- a/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
+++ b/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
@@ -18,5 +18,5 @@
android:shape="rectangle">
<corners android:radius="@dimen/dialogCornerRadius" />
- <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+ <solid android:color="?attr/materialColorSurfaceBright"/>
</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_wellbeing_toast.xml b/quickstep/res/drawable/bg_wellbeing_toast.xml
index 0b66849..418caae 100644
--- a/quickstep/res/drawable/bg_wellbeing_toast.xml
+++ b/quickstep/res/drawable/bg_wellbeing_toast.xml
@@ -16,6 +16,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/materialColorSecondaryFixed" />
+ <solid android:color="?attr/materialColorSecondaryFixed" />
<corners android:radius="?android:attr/dialogCornerRadius" />
</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_chevron_down.xml b/quickstep/res/drawable/ic_chevron_down.xml
index f246cbc..b586e50 100644
--- a/quickstep/res/drawable/ic_chevron_down.xml
+++ b/quickstep/res/drawable/ic_chevron_down.xml
@@ -19,7 +19,7 @@
android:width="48dp"
android:height="48dp"
android:autoMirrored="true"
- android:tint="?androidprv:attr/materialColorOnSurface"
+ android:tint="?attr/materialColorOnSurface"
android:viewportHeight="48"
android:viewportWidth="48">
<group
diff --git a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml b/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
index 2a4f087..8180293 100644
--- a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
+++ b/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
@@ -17,6 +17,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/materialColorSurfaceBright" />
+ <solid android:color="?attr/materialColorSurfaceBright" />
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
</shape>
diff --git a/quickstep/res/drawable/rotate_tutorial_warning.xml b/quickstep/res/drawable/rotate_tutorial_warning.xml
index d1117a4..90b7d64 100644
--- a/quickstep/res/drawable/rotate_tutorial_warning.xml
+++ b/quickstep/res/drawable/rotate_tutorial_warning.xml
@@ -21,6 +21,6 @@
android:viewportHeight="960"
android:viewportWidth="960">
<path
- android:fillColor="?androidprv:attr/materialColorOnSurface"
+ android:fillColor="?attr/materialColorOnSurface"
android:pathData="M40,840L480,80L920,840L40,840ZM178,760L782,760L480,240L178,760ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM440,600L520,600L520,400L440,400L440,600ZM480,500L480,500L480,500L480,500Z" />
</vector>
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index 38df756..613edac 100644
--- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
@@ -23,7 +23,7 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
- launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
index e4942ae..40d2322 100644
--- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
@@ -20,7 +20,7 @@
android:theme="@style/GestureTutorialActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?androidprv:attr/materialColorSurfaceContainer"
+ android:background="?attr/materialColorSurfaceContainer"
android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -163,7 +163,7 @@
android:layout_marginVertical="16dp"
android:text="@string/gesture_tutorial_action_button_label"
android:background="@drawable/gesture_tutorial_action_button_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:backgroundTint="?attr/materialColorPrimary"
android:stateListAnimator="@null"
app:layout_constraintTop_toBottomOf="@id/guideline"
diff --git a/quickstep/res/layout/customizable_taskbar.xml b/quickstep/res/layout/customizable_taskbar.xml
new file mode 100644
index 0000000..e1a80ae
--- /dev/null
+++ b/quickstep/res/layout/customizable_taskbar.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.taskbar.TaskbarDragLayer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/taskbar_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false">
+
+ <!-- TODO(b/349885828) : to be removed in future cl. -->
+ <com.android.launcher3.taskbar.TaskbarView
+ android:id="@+id/taskbar_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:focusable="true"
+ android:importantForAccessibility="yes"
+ android:forceHasOverlappingRendering="false"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="@dimen/transient_taskbar_bottom_margin"
+ android:clipChildren="false" />
+
+ <com.android.launcher3.taskbar.customization.CustomizableTaskbarView
+ android:id="@+id/customizable_taskbar_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:focusable="true"
+ android:importantForAccessibility="yes"
+ android:forceHasOverlappingRendering="false"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="@dimen/transient_taskbar_bottom_margin"
+ android:clipChildren="false" />
+
+ <com.android.launcher3.taskbar.TaskbarScrimView
+ android:id="@+id/taskbar_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <com.android.launcher3.taskbar.bubbles.BubbleBarView
+ android:id="@+id/taskbar_bubbles"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
+ android:layout_gravity="bottom|end"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
+ android:paddingEnd="@dimen/taskbar_icon_spacing"
+ android:paddingStart="@dimen/taskbar_icon_spacing"
+ android:visibility="gone"
+ android:gravity="center"
+ android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation"
+ />
+
+ <com.android.launcher3.taskbar.navbutton.NearestTouchFrame
+ android:id="@+id/navbuttons_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom" >
+
+ <FrameLayout
+ android:id="@+id/start_contextual_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/taskbar_contextual_button_padding"
+ android:paddingEnd="@dimen/taskbar_contextual_button_padding"
+ android:paddingTop="@dimen/taskbar_contextual_padding_top"
+ android:gravity="center_vertical"
+ android:layout_gravity="start"/>
+
+ <LinearLayout
+ android:id="@+id/end_nav_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_gravity="end"/>
+
+ <FrameLayout
+ android:id="@+id/end_contextual_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/taskbar_contextual_padding_top"
+ android:gravity="center_vertical"
+ android:layout_gravity="end"/>
+ </com.android.launcher3.taskbar.navbutton.NearestTouchFrame>
+
+ <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="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/taskbar_stashed_handle_dark_color"
+ android:clipToOutline="true"
+ android:layout_gravity="bottom"/>
+
+ <com.android.launcher3.taskbar.StashedHandleView
+ android:id="@+id/stashed_bubble_handle"
+ tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:background="@color/taskbar_stashed_handle_dark_color"
+ android:clipToOutline="true"
+ android:layout_gravity="bottom"/>
+
+</com.android.launcher3.taskbar.TaskbarDragLayer>
\ No newline at end of file
diff --git a/quickstep/res/layout/customizable_taskbar_view.xml b/quickstep/res/layout/customizable_taskbar_view.xml
new file mode 100644
index 0000000..f55e1d0
--- /dev/null
+++ b/quickstep/res/layout/customizable_taskbar_view.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="@id/guideline1"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.1" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.5" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.7" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline4"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.9" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/digital_wellbeing_toast.xml b/quickstep/res/layout/digital_wellbeing_toast.xml
index 9144c7f..6a99a3b 100644
--- a/quickstep/res/layout/digital_wellbeing_toast.xml
+++ b/quickstep/res/layout/digital_wellbeing_toast.xml
@@ -24,7 +24,7 @@
android:forceHasOverlappingRendering="false"
android:gravity="center"
android:importantForAccessibility="noHideDescendants"
- android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
+ android:textColor="?attr/materialColorOnSecondaryFixed"
android:textSize="14sp"
android:autoSizeTextType="uniform"
android:autoSizeMaxTextSize="14sp"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml
index 668a2e1..7feb882 100644
--- a/quickstep/res/layout/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml
@@ -20,7 +20,7 @@
android:theme="@style/GestureTutorialActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?androidprv:attr/materialColorSurfaceContainer"
+ android:background="?attr/materialColorSurfaceContainer"
android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -161,7 +161,7 @@
android:layout_marginVertical="16dp"
android:text="@string/gesture_tutorial_action_button_label"
android:background="@drawable/gesture_tutorial_action_button_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:backgroundTint="?attr/materialColorPrimary"
android:stateListAnimator="@null"
app:layout_constraintTop_toBottomOf="@id/guideline"
diff --git a/quickstep/res/layout/icon_app_chip_view.xml b/quickstep/res/layout/icon_app_chip_view.xml
index fb9bf99..36ece2a 100644
--- a/quickstep/res/layout/icon_app_chip_view.xml
+++ b/quickstep/res/layout/icon_app_chip_view.xml
@@ -26,7 +26,7 @@
android:importantForAccessibility="no"
android:autoMirrored="true"
android:elevation="@dimen/task_thumbnail_icon_menu_elevation"
- android:background="?androidprv:attr/materialColorSurfaceBright">
+ android:background="?attr/materialColorSurfaceBright">
<!-- ignoring warning because the user of the anchor is a Rect where RTL is not needed -->
<!-- This anchor's bounds is in the expected location after rotations and translations are
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index c0ace9a..8f09176 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
@@ -23,7 +23,7 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
- launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
diff --git a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
index e48794e..c76a2e3 100644
--- a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
@@ -22,7 +22,7 @@
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:clipToOutline="true"
android:importantForAccessibility="yes"
- launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
@@ -40,7 +40,7 @@
android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
android:layout_marginBottom="8dp"
- android:tint="?androidprv:attr/materialColorOnSurface"
+ android:tint="?attr/materialColorOnSurface"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 2bba788..2420a46 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -43,7 +43,7 @@
android:layout_height="@dimen/keyboard_quick_switch_no_recent_items_icon_size"
android:layout_marginBottom="@dimen/keyboard_quick_switch_no_recent_items_icon_margin"
android:src="@drawable/view_carousel"
- android:tint="?androidprv:attr/materialColorOnSurface"
+ android:tint="?attr/materialColorOnSurface"
android:importantForAccessibility="no"
app:layout_constraintVertical_chainStyle="packed"
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 3380ea4..40f38f4 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -23,6 +23,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/recents_clear_all"
- android:textColor="?androidprv:attr/materialColorOnSurface"
- launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
+ android:textColor="?attr/materialColorOnSurface"
+ launcher:focusBorderColor="?attr/materialColorOutline"
android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 46c1332..ac1a50a 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -25,8 +25,8 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
- launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
- launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
+ launcher:focusBorderColor="?attr/materialColorOutline"
+ launcher:hoverBorderColor="?attr/materialColorPrimary">
<include layout="@layout/task_thumbnail_deprecated" />
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 453057c..64aa7e1 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -25,8 +25,8 @@
android:clipChildren="true"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
- launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
- launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary"
+ launcher:focusBorderColor="?attr/materialColorOutline"
+ launcher:hoverBorderColor="?attr/materialColorPrimary"
android:clipToPadding="true"
android:padding="0.1dp">
<!-- Setting a padding of 0.1 dp since android:clipToPadding needs a non-zero value for
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 708aa3c..da2b29f 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -30,8 +30,8 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
- launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
- launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
+ launcher:focusBorderColor="?attr/materialColorOutline"
+ launcher:hoverBorderColor="?attr/materialColorPrimary">
<include layout="@layout/task_thumbnail_deprecated"/>
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index 34640e6..b1fe89e 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -23,6 +23,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
+ android:scaleType="matrix"
android:visibility="gone"/>
<com.android.quickstep.task.thumbnail.LiveTileView
diff --git a/quickstep/res/layout/task_view_menu_option.xml b/quickstep/res/layout/task_view_menu_option.xml
index ffe2401..5218de0 100644
--- a/quickstep/res/layout/task_view_menu_option.xml
+++ b/quickstep/res/layout/task_view_menu_option.xml
@@ -31,7 +31,7 @@
android:layout_height="@dimen/system_shortcut_icon_size"
android:layout_marginStart="@dimen/task_menu_option_start_margin"
android:layout_gravity="center_horizontal"
- android:backgroundTint="?androidprv:attr/materialColorOnSurface"/>
+ android:backgroundTint="?attr/materialColorOnSurface"/>
<TextView
style="@style/BaseIcon"
@@ -40,7 +40,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/task_menu_option_text_start_margin"
android:textSize="14sp"
- android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textColor="?attr/materialColorOnSurface"
android:focusable="false"
android:gravity="start"
android:ellipsize="end" />
diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml
index 94100ba..2052446 100644
--- a/quickstep/res/values-night/colors.xml
+++ b/quickstep/res/values-night/colors.xml
@@ -26,6 +26,6 @@
<!-- Turn on work apps button -->
<color name="work_turn_on_stroke">?androidprv:attr/colorAccentSecondaryVariant</color>
- <color name="work_fab_bg_color">?androidprv:attr/materialColorPrimaryFixedDim</color>
- <color name="work_fab_icon_color">?androidprv:attr/materialColorOnPrimaryFixed</color>
+ <color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
+ <color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-night/styles.xml b/quickstep/res/values-night/styles.xml
index 401351f..2cb633a 100644
--- a/quickstep/res/values-night/styles.xml
+++ b/quickstep/res/values-night/styles.xml
@@ -82,16 +82,16 @@
<style name="GestureTutorialActivity" parent="@style/AppTheme">
<item name="background">@android:color/transparent</item>
<item name="tutorialSubtitle">@android:color/white</item>
- <item name="surfaceContainer">?androidprv:attr/materialColorSurfaceContainer</item>
- <item name="onSurfaceHome">?androidprv:attr/materialColorPrimaryFixedDim</item>
- <item name="surfaceHome">?androidprv:attr/materialColorOnPrimaryFixedVariant</item>
- <item name="secondaryHome">?androidprv:attr/materialColorOnPrimaryFixed</item>
- <item name="onSurfaceBack">?androidprv:attr/materialColorTertiaryFixedDim</item>
- <item name="surfaceBack">?androidprv:attr/materialColorOnTertiaryFixedVariant</item>
- <item name="secondaryBack">?androidprv:attr/materialColorOnTertiaryFixed</item>
- <item name="onSurfaceOverview">?androidprv:attr/materialColorPrimaryFixed</item>
- <item name="surfaceOverview">?androidprv:attr/materialColorOnSecondaryFixedVariant</item>
- <item name="secondaryOverview">?androidprv:attr/materialColorOnSecondaryFixed</item>
+ <item name="surfaceContainer">?attr/materialColorSurfaceContainer</item>
+ <item name="onSurfaceHome">?attr/materialColorPrimaryFixedDim</item>
+ <item name="surfaceHome">?attr/materialColorOnPrimaryFixedVariant</item>
+ <item name="secondaryHome">?attr/materialColorOnPrimaryFixed</item>
+ <item name="onSurfaceBack">?attr/materialColorTertiaryFixedDim</item>
+ <item name="surfaceBack">?attr/materialColorOnTertiaryFixedVariant</item>
+ <item name="secondaryBack">?attr/materialColorOnTertiaryFixed</item>
+ <item name="onSurfaceOverview">?attr/materialColorPrimaryFixed</item>
+ <item name="surfaceOverview">?attr/materialColorOnSecondaryFixedVariant</item>
+ <item name="secondaryOverview">?attr/materialColorOnSecondaryFixed</item>
</style>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index ed3ebee..47d8055 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -122,7 +122,7 @@
<string name="taskbar_edu_close" msgid="887022990168191073">"மூடுக"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"முடிந்தது"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"முகப்பு"</string>
- <string name="taskbar_button_a11y" msgid="5241161324875094465">"அணுகல்தன்மை"</string>
+ <string name="taskbar_button_a11y" msgid="5241161324875094465">"மாற்றுத்திறன் வசதி"</string>
<string name="taskbar_button_back" msgid="8558862226461164514">"பின்செல்லும்"</string>
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME சுவிட்ச்சர்"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"சமீபத்தியவை"</string>
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 0f997f9..0bb971e 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -95,6 +95,6 @@
<!-- Turn on work apps button -->
<color name="work_turn_on_stroke">?androidprv:attr/colorAccentPrimaryVariant</color>
- <color name="work_fab_bg_color">?androidprv:attr/materialColorPrimaryFixedDim</color>
- <color name="work_fab_icon_color">?androidprv:attr/materialColorOnPrimaryFixed</color>
+ <color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
+ <color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index d4f66e2..867ce17 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -449,11 +449,13 @@
<dimen name="bubblebar_icon_size_small">32dp</dimen>
<dimen name="bubblebar_icon_size">36dp</dimen>
+ <dimen name="bubblebar_icon_size_persistent_taskbar">28dp</dimen>
<dimen name="bubblebar_badge_size">24dp</dimen>
<dimen name="bubblebar_icon_overlap">12dp</dimen>
<dimen name="bubblebar_overflow_inset">16dp</dimen>
<dimen name="bubblebar_icon_spacing">6dp</dimen>
<dimen name="bubblebar_icon_spacing_large">8dp</dimen>
+ <dimen name="bubblebar_icon_spacing_persistent_taskbar">@dimen/bubblebar_icon_spacing</dimen>
<dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
<dimen name="bubblebar_icon_elevation">1dp</dimen>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 952505a..1f4720c 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -124,7 +124,7 @@
<style name="TextAppearance.GestureTutorial.ButtonLabel"
parent="TextAppearance.GestureTutorial.CallToAction">
<item name="android:gravity">center</item>
- <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item>
+ <item name="android:textColor">?attr/materialColorOnPrimary</item>
<item name="android:letterSpacing">0.02</item>
<item name="android:textSize">16sp</item>
<item name="android:textAllCaps">false</item>
@@ -268,53 +268,59 @@
<style name="KeyboardQuickSwitchText">
<item name="fontFamily">google-sans-text</item>
<item name="android:textSize">14sp</item>
- <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:textColor">?attr/materialColorOnSurface</item>
<item name="lineHeight">20sp</item>
</style>
<style name="KeyboardQuickSwitchText.OnBackground" parent="KeyboardQuickSwitchText">
- <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:textColor">?attr/materialColorOnSurface</item>
</style>
<style name="GestureTutorialActivity" parent="@style/AppTheme">
<item name="background">@android:color/transparent</item>
<item name="tutorialSubtitle">@android:color/black</item>
- <item name="surfaceContainer">?androidprv:attr/materialColorSurfaceContainer</item>
- <item name="onSurfaceHome">?androidprv:attr/materialColorPrimaryFixed</item>
+ <item name="surfaceContainer">?attr/materialColorSurfaceContainer</item>
+ <item name="onSurfaceHome">?attr/materialColorPrimaryFixed</item>
<item name="surfaceHome">@android:color/system_accent1_300</item>
- <item name="secondaryHome">?androidprv:attr/materialColorOnPrimaryFixedVariant</item>
- <item name="onSurfaceBack">?androidprv:attr/materialColorTertiaryFixed</item>
+ <item name="secondaryHome">?attr/materialColorOnPrimaryFixedVariant</item>
+ <item name="onSurfaceBack">?attr/materialColorTertiaryFixed</item>
<item name="surfaceBack">@android:color/system_accent3_300</item>
- <item name="secondaryBack">?androidprv:attr/materialColorOnTertiaryFixedVariant</item>
- <item name="onSurfaceOverview">?androidprv:attr/materialColorPrimaryFixed</item>
+ <item name="secondaryBack">?attr/materialColorOnTertiaryFixedVariant</item>
+ <item name="onSurfaceOverview">?attr/materialColorPrimaryFixed</item>
<item name="surfaceOverview">@android:color/system_accent2_300</item>
- <item name="secondaryOverview">?androidprv:attr/materialColorOnSecondaryFixedVariant</item>
+ <item name="secondaryOverview">?attr/materialColorOnSecondaryFixedVariant</item>
</style>
<style name="rotate_prompt_title" parent="TextAppearance.GestureTutorial.Dialog.Title">
- <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:textColor">?attr/materialColorOnSurface</item>
</style>
<style name="rotate_prompt_subtitle" parent="TextAppearance.GestureTutorial.Dialog.Subtitle">
- <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+ <item name="android:textColor">?attr/materialColorOnSurfaceVariant</item>
</style>
<style name="ArrowTipTaskbarStyle">
- <item name="arrowTipBackground">?androidprv:attr/materialColorSurfaceContainer</item>
- <item name="arrowTipTextColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="arrowTipBackground">?attr/materialColorSurfaceContainer</item>
+ <item name="arrowTipTextColor">?attr/materialColorOnSurface</item>
</style>
<style name="IconAppChipMenuTextStyle">
<item name="android:fontFamily">google-sans-text-medium</item>
<item name="android:textSize">@dimen/task_thumbnail_icon_menu_text_size</item>
- <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:textColor">?attr/materialColorOnSurface</item>
<item name="android:letterSpacing">0.025</item>
<item name="android:lineHeight">20sp</item>
</style>
- <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
- <item name="widgetsTheme">@style/WidgetContainerTheme</item>
+ <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowAnimationStyle">@android:style/Animation</item>
+
+ <item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
</style>
</resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5a74f4a..44601b7 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -356,6 +356,14 @@
options.setOnAnimationAbortListener(endCallback);
options.setOnAnimationFinishedListener(endCallback);
+ // Prepare taskbar for animation synchronization. This needs to happen here before any
+ // app transition is created.
+ LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+ if (enableScalingRevealHomeAnimation() && taskbarController != null) {
+ taskbarController.setIgnoreInAppFlagForSync(true);
+ onEndCallback.add(() -> taskbarController.setIgnoreInAppFlagForSync(false));
+ }
+
IBinder cookie = mAppLaunchRunner.supportsReturnTransition()
? ((ContainerAnimationRunner) mAppLaunchRunner).getCookie() : null;
addLaunchCookie(cookie, (ItemInfo) v.getTag(), options);
@@ -1644,7 +1652,15 @@
}
private void addCujInstrumentation(Animator anim, int cuj) {
- anim.addListener(new AnimationSuccessListener() {
+ anim.addListener(getCujAnimationSuccessListener(cuj));
+ }
+
+ private void addCujInstrumentation(RectFSpringAnim anim, int cuj) {
+ anim.addAnimatorListener(getCujAnimationSuccessListener(cuj));
+ }
+
+ private AnimationSuccessListener getCujAnimationSuccessListener(int cuj) {
+ return new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
mDragLayer.getViewTreeObserver().addOnDrawListener(
@@ -1678,7 +1694,7 @@
public void onAnimationSuccess(Animator animator) {
InteractionJankMonitorWrapper.end(cuj);
}
- });
+ };
}
/**
@@ -1759,10 +1775,6 @@
// invisibility on touch down, and only reset it after the animation to home
// is initialized.
if (launcherIsForceInvisibleOrOpening || fromPredictiveBack) {
- addCujInstrumentation(anim, playFallBackAnimation
- ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
- : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
-
AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -1772,6 +1784,14 @@
}
};
+ if (rectFSpringAnim != null && anim.getChildAnimations().isEmpty()) {
+ addCujInstrumentation(rectFSpringAnim, Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
+ } else {
+ addCujInstrumentation(anim, playFallBackAnimation
+ ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
+ : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
+ }
+
if (fromPredictiveBack && rectFSpringAnim != null) {
rectFSpringAnim.addAnimatorListener(endListener);
} else {
@@ -1912,6 +1932,21 @@
anim.addListener(mForceInvisibleListener);
}
+ // Syncs the app launch animation and taskbar stash animation (if exists).
+ if (enableScalingRevealHomeAnimation()) {
+ LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+ if (taskbarController != null) {
+ taskbarController.setIgnoreInAppFlagForSync(false);
+
+ if (launcherClosing) {
+ Animator taskbar = taskbarController.createAnimToApp();
+ if (taskbar != null) {
+ anim.play(taskbar);
+ }
+ }
+ }
+ }
+
result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy,
skipFirstFrame);
}
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 44d8a5c..0b18633 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -28,6 +28,7 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
@@ -46,11 +47,11 @@
import com.android.launcher3.model.WidgetPredictionsRequester;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
-import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
@@ -59,9 +60,8 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
@@ -110,7 +110,8 @@
private WidgetsModel mModel;
private LauncherAppState mApp;
private WidgetPredictionsRequester mWidgetPredictionsRequester;
- private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
+ private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {
+ });
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
@@ -128,9 +129,23 @@
/** A set of user ids that should be filtered out from the selected widgets. */
@NonNull
Set<Integer> mFilteredUserIds = new HashSet<>();
+
@Nullable
private WidgetsFullSheet mWidgetSheet;
+ private final Predicate<WidgetItem> mWidgetsFilter = widget -> {
+ final WidgetAcceptabilityVerdict verdict =
+ isWidgetAcceptable(widget, /* applySizeFilter=*/ false);
+ verdict.maybeLogVerdict();
+ return verdict.isAcceptable;
+ };
+ private final Predicate<WidgetItem> mDefaultWidgetsFilter = widget -> {
+ final WidgetAcceptabilityVerdict verdict =
+ isWidgetAcceptable(widget, /* applySizeFilter=*/ true);
+ verdict.maybeLogVerdict();
+ return verdict.isAcceptable;
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -271,47 +286,41 @@
};
}
- /** Updates the model with widgets, applies filters and launches the widgets sheet once
- * widgets are available */
+ /**
+ * Updates the model with widgets, applies filters and launches the widgets sheet once
+ * widgets are available
+ */
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
+ Context context = app.getContext();
+
mModel.update(app, null);
- final List<WidgetsListBaseEntry> allWidgets =
- mModel.getFilteredWidgetsListForPicker(
- app.getContext(),
- /*widgetItemFilter=*/ widget -> {
- final WidgetAcceptabilityVerdict verdict =
- isWidgetAcceptable(widget);
- verdict.maybeLogVerdict();
- return verdict.isAcceptable;
- }
- );
- bindWidgets(allWidgets);
+ bindWidgets(mModel.getWidgetsByPackageItem());
// Open sheet once widgets are available, so that it doesn't interrupt the open
// animation.
openWidgetsSheet();
if (mUiSurface != null) {
- Map<ComponentKey, WidgetItem> allWidgetItems = allWidgets.stream()
- .filter(entry -> entry instanceof WidgetsListContentEntry)
- .flatMap(entry -> entry.mWidgets.stream())
- .distinct()
- .collect(Collectors.toMap(
- widget -> new ComponentKey(widget.componentName, widget.user),
- Function.identity()
- ));
mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
- mUiSurface, allWidgetItems);
+ mUiSurface, mModel.getWidgetsByComponentKey());
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
});
}
- private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
- MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
+ private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets) {
+ WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder(
+ mApp.getContext());
+
+ final List<WidgetsListBaseEntry> allWidgets = builder.build(widgets, mWidgetsFilter);
+ final List<WidgetsListBaseEntry> defaultWidgets =
+ shouldShowDefaultWidgets() ? builder.build(widgets,
+ mDefaultWidgetsFilter) : List.of();
+
+ MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(allWidgets, defaultWidgets));
}
- private void openWidgetsSheet() {
+ private void openWidgetsSheet() {
MAIN_EXECUTOR.execute(() -> {
mWidgetSheet = WidgetsFullSheet.show(this, true);
mWidgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
@@ -378,9 +387,15 @@
mActiveOnBackAnimationCallback.onBackCancelled();
mActiveOnBackAnimationCallback = null;
}
- };
+ }
- private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
+ private boolean shouldShowDefaultWidgets() {
+ // If optional filters such as size filter are present, we display them as default widgets.
+ return mDesiredWidgetWidth != 0 || mDesiredWidgetHeight != 0;
+ }
+
+ private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget,
+ boolean applySizeFilter) {
final AppWidgetProviderInfo info = widget.widgetInfo;
if (info == null) {
return rejectWidget(widget, "shortcut");
@@ -401,61 +416,63 @@
info.widgetCategory);
}
- if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
- // Accept the widget if the desired dimensions are unspecified.
- return acceptWidget(widget);
- }
-
- final boolean isHorizontallyResizable =
- (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
- if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
- if (info.maxResizeWidth > 0
- && info.maxResizeWidth >= info.minWidth
- && info.maxResizeWidth < mDesiredWidgetWidth) {
- return rejectWidget(
- widget,
- "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
- info.maxResizeWidth,
- mDesiredWidgetWidth);
+ if (applySizeFilter) {
+ if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
+ // Accept the widget if the desired dimensions are unspecified.
+ return acceptWidget(widget);
}
- final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
- if (minWidth > mDesiredWidgetWidth) {
- return rejectWidget(
- widget,
- "min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]",
- info.minWidth,
- info.minResizeWidth,
- mDesiredWidgetWidth);
- }
- }
+ final boolean isHorizontallyResizable =
+ (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
+ if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
+ if (info.maxResizeWidth > 0
+ && info.maxResizeWidth >= info.minWidth
+ && info.maxResizeWidth < mDesiredWidgetWidth) {
+ return rejectWidget(
+ widget,
+ "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
+ info.maxResizeWidth,
+ mDesiredWidgetWidth);
+ }
- final boolean isVerticallyResizable =
- (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
- if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
- if (info.maxResizeHeight > 0
- && info.maxResizeHeight >= info.minHeight
- && info.maxResizeHeight < mDesiredWidgetHeight) {
- return rejectWidget(
- widget,
- "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
- info.maxResizeHeight,
- mDesiredWidgetHeight);
+ final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
+ if (minWidth > mDesiredWidgetWidth) {
+ return rejectWidget(
+ widget,
+ "min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]",
+ info.minWidth,
+ info.minResizeWidth,
+ mDesiredWidgetWidth);
+ }
}
- final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
- if (minHeight > mDesiredWidgetHeight) {
- return rejectWidget(
- widget,
- "min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]",
- info.minHeight,
- info.minResizeHeight,
- mDesiredWidgetHeight);
- }
- }
+ final boolean isVerticallyResizable =
+ (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
+ if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
+ if (info.maxResizeHeight > 0
+ && info.maxResizeHeight >= info.minHeight
+ && info.maxResizeHeight < mDesiredWidgetHeight) {
+ return rejectWidget(
+ widget,
+ "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
+ info.maxResizeHeight,
+ mDesiredWidgetHeight);
+ }
- if (!isHorizontallyResizable || !isVerticallyResizable) {
- return rejectWidget(widget, "not resizeable");
+ final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
+ if (minHeight > mDesiredWidgetHeight) {
+ return rejectWidget(
+ widget,
+ "min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]",
+ info.minHeight,
+ info.minResizeHeight,
+ mDesiredWidgetHeight);
+ }
+ }
+
+ if (!isHorizontallyResizable || !isVerticallyResizable) {
+ return rejectWidget(widget, "not resizeable");
+ }
}
return acceptWidget(widget);
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 84c2ed2..7a8b58e 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -31,12 +31,12 @@
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.ColorInt;
-import androidx.core.content.ContextCompat;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
+import com.android.launcher3.util.Themes;
/**
* A view which shows a horizontal divider
@@ -84,10 +84,9 @@
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height)
};
- mStrokeColor = ContextCompat.getColor(context, R.color.material_color_outline_variant);
+ mStrokeColor = Themes.getAttrColor(context, R.attr.materialColorOutlineVariant);
- mAllAppsLabelTextColor = ContextCompat.getColor(context,
- R.color.material_color_on_surface_variant);
+ mAllAppsLabelTextColor = Themes.getAttrColor(context, R.attr.materialColorOnSurfaceVariant);
mAccessibilityManager = AccessibilityManager.getInstance(context);
setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context));
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 64bb05e..0395d32 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -65,7 +65,7 @@
Collectors.toSet());
Predicate<WidgetItem> notOnWorkspace = w -> !widgetsInWorkspace.contains(w);
Map<ComponentKey, WidgetItem> allWidgets =
- dataModel.widgetsModel.getAllWidgetComponentsWithoutShortcuts();
+ dataModel.widgetsModel.getWidgetsByComponentKey();
List<WidgetItem> servicePredictedItems = new ArrayList<>();
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 779009a..0add1c4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IGNORE_IN_APP;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
@@ -256,6 +257,24 @@
return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
}
+ /**
+ * Create Taskbar animation to be played alongside the Launcher app launch animation.
+ */
+ public @Nullable Animator createAnimToApp() {
+ TaskbarStashController stashController = mControllers.taskbarStashController;
+ stashController.updateStateForFlag(TaskbarStashController.FLAG_IN_APP, true);
+ return stashController.createApplyStateAnimator(stashController.getStashDuration());
+ }
+
+ /**
+ * Temporarily ignore FLAG_IN_APP for app launches to prevent premature taskbar stashing.
+ * This is needed because taskbar gets a signal to stash before we actually start the
+ * app launch animation.
+ */
+ public void setIgnoreInAppFlagForSync(boolean enabled) {
+ mControllers.taskbarStashController.updateStateForFlag(FLAG_IGNORE_IN_APP, enabled);
+ }
+
public void updateTaskbarLauncherStateGoingHome() {
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, true);
mTaskbarLauncherStateController.applyState();
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 0fa3fbc..ea2adcf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -48,7 +48,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
-import static com.android.wm.shell.Flags.enableTaskbarOnPhones;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
@@ -80,6 +79,7 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.inputmethod.Flags;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -247,7 +247,7 @@
? context.getColor(R.color.taskbar_nav_icon_light_color)
: context.getColor(R.color.taskbar_nav_icon_dark_color);
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions = new TaskbarTransitions(mContext, mNavButtonsView);
}
}
@@ -274,7 +274,10 @@
InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
if (!mIsImeRenderingNavButtons) {
// IME switcher
- mImeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
+ final int switcherResId = Flags.imeSwitcherRevamp()
+ ? com.android.internal.R.drawable.ic_ime_switcher_new
+ : R.drawable.ic_ime_switcher;
+ mImeSwitcherButton = addButton(switcherResId, BUTTON_IME_SWITCH,
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
mControllers.navButtonController, R.id.ime_switcher);
mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
@@ -361,7 +364,7 @@
R.bool.floating_rotation_button_position_left);
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
mRotationButtonListener);
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions.init();
}
@@ -369,7 +372,7 @@
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
// Initialize things needed to move nav buttons to separate window.
- mSeparateWindowParent = new BaseDragLayer<TaskbarActivityContext>(mContext, null, 0) {
+ mSeparateWindowParent = new BaseDragLayer<>(mContext, null, 0) {
@Override
public void recreateControllers() {
mControllers = new TouchController[0];
@@ -625,7 +628,7 @@
}
public void setWallpaperVisible(boolean isVisible) {
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions.setWallpaperVisibility(isVisible);
}
}
@@ -638,20 +641,20 @@
}
public void checkNavBarModes() {
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
boolean isBarHidden = (mSysuiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
mTaskbarTransitions.transitionTo(mTransitionMode, !isBarHidden);
}
}
public void finishBarAnimations() {
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions.finishAnimations();
}
}
public void touchAutoDim(boolean reset) {
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions.setAutoDim(false);
mHandler.removeCallbacks(mAutoDim);
if (reset) {
@@ -661,7 +664,7 @@
}
public void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) {
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions.transitionTo(barMode, animate);
}
}
@@ -765,7 +768,7 @@
private void onDarkIntensityChanged() {
updateNavButtonColor();
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions.onDarkIntensityChanged(mTaskbarNavButtonDarkIntensity.value);
}
}
@@ -1115,7 +1118,7 @@
+ mOnBackgroundNavButtonColorOverrideMultiplier.value);
mNavButtonsView.dumpLogs(prefix + "\t", pw);
- if (enableTaskbarOnPhones() && mContext.isPhoneButtonNavMode()) {
+ if (mContext.isPhoneMode()) {
mTaskbarTransitions.dumpLogs(prefix + "\t", pw);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 78c6d4b..3048243 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -112,6 +112,8 @@
import com.android.launcher3.taskbar.bubbles.BubblePinController;
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
+import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator;
+import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator;
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.testing.TestLogging;
@@ -205,6 +207,10 @@
private final LauncherPrefs mLauncherPrefs;
+ private final TaskbarFeatureEvaluator mTaskbarFeatureEvaluator;
+
+ private final TaskbarSpecsEvaluator mTaskbarSpecsEvaluator;
+
public TaskbarActivityContext(Context windowContext,
@Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
@@ -215,6 +221,13 @@
applyDeviceProfile(launcherDp);
final Resources resources = getResources();
+ mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
+ mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
+ this,
+ mTaskbarFeatureEvaluator,
+ mDeviceProfile.inv.numRows,
+ mDeviceProfile.inv.numColumns);
+
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
@@ -1649,6 +1662,14 @@
return mControllers.taskbarStashController.isInStashedLauncherState();
}
+ public TaskbarFeatureEvaluator getTaskbarFeatureEvaluator() {
+ return mTaskbarFeatureEvaluator;
+ }
+
+ public TaskbarSpecsEvaluator getTaskbarSpecsEvaluator() {
+ return mTaskbarSpecsEvaluator;
+ }
+
protected void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarActivityContext:");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
index 7f9d8a3..19e9872 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
@@ -53,8 +53,7 @@
private val activityContext: ActivityContext = ActivityContext.lookupContext(context)
- private val backgroundColor =
- Themes.getAttrColor(context, com.android.internal.R.attr.materialColorSurfaceBright)
+ private val backgroundColor = Themes.getAttrColor(context, R.attr.materialColorSurfaceBright)
private val tooltipCornerRadius = Themes.getDialogCornerRadius(context)
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 267e19c..0c5ad42 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -95,6 +95,7 @@
public static final int FLAG_STASHED_SYSUI = 1 << 9; // app pinning,...
public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ...
public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview
+ public static final int FLAG_IGNORE_IN_APP = 1 << 12; // used to sync with app launch animation
// If any of these flags are enabled, isInApp should return true.
private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -331,9 +332,10 @@
applyState(/* duration = */ 0);
// Hide the background while stashed so it doesn't show on fast swipes home
- boolean shouldHideTaskbarBackground = enableScalingRevealHomeAnimation()
- && DisplayController.isTransientTaskbar(mActivity)
- && isStashed();
+ boolean shouldHideTaskbarBackground = mActivity.isPhoneMode() ||
+ (enableScalingRevealHomeAnimation()
+ && DisplayController.isTransientTaskbar(mActivity)
+ && isStashed());
mTaskbarBackgroundAlphaForStash.setValue(shouldHideTaskbarBackground ? 0 : 1);
@@ -1262,6 +1264,11 @@
*/
@Nullable
public Animator createSetStateAnimator(long flags, long duration) {
+ // We do this when we want to synchronize the app launch and taskbar stash animations.
+ if (hasAnyFlag(FLAG_IGNORE_IN_APP) && hasAnyFlag(flags, FLAG_IN_APP)) {
+ flags = flags & ~FLAG_IN_APP;
+ }
+
boolean isStashed = mStashCondition.test(flags);
if (DEBUG) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 527e3a3..b21c414 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -46,6 +46,7 @@
import android.view.animation.Interpolator;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.view.OneShotPreDrawListener;
import com.android.app.animation.Interpolators;
@@ -96,6 +97,8 @@
public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
private static final int NUM_ALPHA_CHANNELS = 7;
+ private static boolean sEnableModelLoadingForTests = true;
+
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
private final MultiValueAlpha mTaskbarIconAlpha;
@@ -192,7 +195,7 @@
mTaskbarIconTranslationXForPinning.updateValue(pinningValue);
mModelCallbacks.init(controllers);
- if (mActivity.isUserSetupComplete()) {
+ if (mActivity.isUserSetupComplete() && sEnableModelLoadingForTests) {
// Only load the callbacks if user setup is completed
LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
}
@@ -924,4 +927,10 @@
mModelCallbacks.dumpLogs(prefix + "\t", pw);
}
+
+ /** Enables model loading for tests. */
+ @VisibleForTesting
+ public static void enableModelLoadingForTests(boolean enable) {
+ sEnableModelLoadingForTests = enable;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index a3832cd..5be0171 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -33,7 +33,6 @@
import android.annotation.BinderThread;
import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
@@ -356,6 +355,13 @@
}
}
+ // if a bubble was updated upstream, but removed before the update was received, add it back
+ if (update.updatedBubble != null && !mBubbles.containsKey(update.updatedBubble.getKey())) {
+ mBubbles.put(update.updatedBubble.getKey(), update.updatedBubble);
+ mBubbleBarViewController.addBubble(
+ update.updatedBubble, isExpanding, suppressAnimation);
+ }
+
if (update.addedBubble != null && isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = update.addedBubble;
@@ -456,15 +462,6 @@
/** Tells WMShell to show the currently selected bubble. */
public void showSelectedBubble() {
if (getSelectedBubbleKey() != null) {
- if (mSelectedBubble instanceof BubbleBarBubble) {
- // Because we've visited this bubble, we should suppress the notification.
- // This is updated on WMShell side when we show the bubble, but that update isn't
- // passed to launcher, instead we apply it directly here.
- BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
- info.setFlags(
- info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
- mSelectedBubble.getView().updateDotVisibility(true /* animate */);
- }
mLastSentBubbleBarTop = mBarView.getRestingTopPositionOnScreen();
mSystemUiProxy.showBubble(getSelectedBubbleKey(), mLastSentBubbleBarTop);
} else {
@@ -638,8 +635,8 @@
final TypedArray ta = mContext.obtainStyledAttributes(
new int[]{
- com.android.internal.R.attr.materialColorOnPrimaryFixed,
- com.android.internal.R.attr.materialColorPrimaryFixed
+ R.attr.materialColorOnPrimaryFixed,
+ R.attr.materialColorPrimaryFixed
});
int overflowIconColor = ta.getColor(0, Color.WHITE);
int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index fd989b1..4794dfd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -240,6 +240,10 @@
BubbleView firstBubble = (BubbleView) getChildAt(0);
mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
}
+ // If the bar was just expanded, remove the dot from the selected bubble.
+ if (mIsBarExpanded && mSelectedBubbleView != null) {
+ mSelectedBubbleView.markSeen();
+ }
updateWidth();
},
/* onUpdate= */ animator -> {
@@ -665,7 +669,7 @@
}
/** Add a new bubble to the bubble bar. */
- public void addBubble(View bubble) {
+ public void addBubble(BubbleView bubble) {
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
Gravity.LEFT);
if (isExpanded()) {
@@ -673,6 +677,7 @@
bubble.setScaleX(0f);
bubble.setScaleY(0f);
addView(bubble, 0, lp);
+ bubble.showDotIfNeeded(/* animate= */ false);
mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
getChildCount(), mBubbleBarLocation.isOnLeft(isLayoutRtl()));
@@ -825,6 +830,23 @@
updateBubbleAccessibilityStates();
updateContentDescription();
mDismissedByDragBubbleView = null;
+ updateNotificationDotsIfCollapsed();
+ }
+
+ private void updateNotificationDotsIfCollapsed() {
+ if (isExpanded()) {
+ return;
+ }
+ for (int i = 0; i < getChildCount(); i++) {
+ BubbleView bubbleView = (BubbleView) getChildAt(i);
+ // when we're collapsed, the first bubble should show the dot if it has it. the rest of
+ // the bubbles should hide their dots.
+ if (i == 0 && bubbleView.hasUnseenContent()) {
+ bubbleView.showDotIfNeeded(/* animate= */ true);
+ } else {
+ bubbleView.hideDot();
+ }
+ }
}
private void updateWidth() {
@@ -865,7 +887,6 @@
float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
// When translating X & Y the scale is ignored, so need to deduct it from the translations
final float ty = bubbleBarAnimatedTop + mBubbleBarPadding - getScaleIconShift();
- final boolean animate = getVisibility() == VISIBLE;
final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
// elevation state is opposite to widthState - when expanded all icons are flat
float elevationState = (1 - widthState);
@@ -897,10 +918,10 @@
bv.setZ(fullElevationForChild * elevationState);
// only update the dot scale if we're expanding or collapsing
- // TODO b/351904597: update the dot for the first bubble after removal and reorder
- // since those might happen when the bar is collapsed and will need their dot back
if (mWidthAnimator.isRunning()) {
- bv.setDotScale(widthState);
+ // The dot for the selected bubble scales in the opposite direction of the expansion
+ // animation.
+ bv.showDotIfNeeded(bv == mSelectedBubbleView ? 1 - widthState : widthState);
}
if (mIsBarExpanded) {
@@ -1025,6 +1046,7 @@
}
updateBubblesLayoutProperties(mBubbleBarLocation);
updateContentDescription();
+ updateNotificationDotsIfCollapsed();
}
}
@@ -1049,6 +1071,14 @@
if (mBubbleAnimator == null) {
updateArrowForSelected(previouslySelectedBubble != null);
}
+ if (view != null) {
+ if (isExpanded()) {
+ view.markSeen();
+ } else {
+ // when collapsed, the selected bubble should show the dot if it has it
+ view.showDotIfNeeded(/* animate= */ true);
+ }
+ }
}
/**
@@ -1316,6 +1346,7 @@
public void dump(PrintWriter pw) {
pw.println("BubbleBarView state:");
pw.println(" visibility: " + getVisibility());
+ pw.println(" alpha: " + getAlpha());
pw.println(" translation Y: " + getTranslationY());
pw.println(" bubbles in bar (childCount = " + getChildCount() + ")");
for (BubbleView bubbleView: getBubbles()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 0f9de16..2311d42 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -31,6 +31,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -62,6 +63,7 @@
private final TaskbarActivityContext mActivity;
private final BubbleBarView mBarView;
private int mIconSize;
+ private int mBubbleBarPadding;
// Initialized in init.
private BubbleStashController mBubbleStashController;
@@ -89,6 +91,8 @@
private BubbleBarViewAnimator mBubbleBarViewAnimator;
+ private TimeSource mTimeSource = System::currentTimeMillis;
+
@Nullable
private BubbleBarBoundsChangeListener mBoundsChangeListener;
@@ -108,12 +112,11 @@
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
-
+ onBubbleBarConfigurationChanged(/* animate= */ false);
mActivity.addOnDeviceProfileChangeListener(
- dp -> updateBubbleBarIconSize(dp.taskbarIconSize, /* animate= */ true));
- updateBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize, /* animate= */ false);
+ dp -> onBubbleBarConfigurationChanged(/* animate= */ true));
mBubbleBarScale.updateValue(1f);
- mBubbleClickListener = v -> onBubbleClicked(v);
+ mBubbleClickListener = v -> onBubbleClicked((BubbleView) v);
mBubbleBarClickListener = v -> onBubbleBarClicked();
mBubbleDragController.setupBubbleBarView(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
@@ -137,8 +140,9 @@
});
}
- private void onBubbleClicked(View v) {
- BubbleBarItem bubble = ((BubbleView) v).getBubble();
+ private void onBubbleClicked(BubbleView bubbleView) {
+ bubbleView.markSeen();
+ BubbleBarItem bubble = bubbleView.getBubble();
if (bubble == null) {
Log.e(TAG, "bubble click listener, bubble was null");
}
@@ -332,27 +336,60 @@
// Modifying view related properties.
//
- private void updateBubbleBarIconSize(int newIconSize, boolean animate) {
+ /** Notifies controller of configuration change, so bubble bar can be adjusted */
+ public void onBubbleBarConfigurationChanged(boolean animate) {
+ int newIconSize;
+ int newPadding;
Resources res = mActivity.getResources();
+ if (mBubbleStashController.isBubblesShowingOnHome()) {
+ newIconSize = getBubbleBarIconSizeFromDeviceProfile(res);
+ newPadding = getBubbleBarPaddingFromDeviceProfile(res);
+ } else {
+ // the bubble bar is shown inside the persistent task bar, use preset sizes
+ newIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_persistent_taskbar);
+ newPadding = res.getDimensionPixelSize(
+ R.dimen.bubblebar_icon_spacing_persistent_taskbar);
+ }
+ updateBubbleBarIconSizeAndPadding(newIconSize, newPadding, animate);
+ }
+
+
+ private int getBubbleBarIconSizeFromDeviceProfile(Resources res) {
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
DisplayMetrics dm = res.getDisplayMetrics();
float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
APP_ICON_SMALL_DP, dm);
float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
APP_ICON_MEDIUM_DP, dm);
- float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- APP_ICON_LARGE_DP, dm);
float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
- float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
- mIconSize = newIconSize <= smallMediumThreshold
+ int taskbarIconSize = deviceProfile.taskbarIconSize;
+ return taskbarIconSize <= smallMediumThreshold
? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
- float bubbleBarPadding = newIconSize >= mediumLargeThreshold
+
+ }
+
+ private int getBubbleBarPaddingFromDeviceProfile(Resources res) {
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ DisplayMetrics dm = res.getDisplayMetrics();
+ float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_MEDIUM_DP, dm);
+ float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ APP_ICON_LARGE_DP, dm);
+ float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
+ return deviceProfile.taskbarIconSize >= mediumLargeThreshold
? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+ }
+
+ private void updateBubbleBarIconSizeAndPadding(int iconSize, int padding, boolean animate) {
+ if (mIconSize == iconSize && mBubbleBarPadding == padding) return;
+ mIconSize = iconSize;
+ mBubbleBarPadding = padding;
if (animate) {
- mBarView.animateBubbleBarIconSize(mIconSize, bubbleBarPadding);
+ mBarView.animateBubbleBarIconSize(iconSize, padding);
} else {
- mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
+ mBarView.setIconSizeAndPadding(iconSize, padding);
}
}
@@ -576,7 +613,7 @@
* @param bubble dismissed bubble item
*/
public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
- mSystemUiProxy.dragBubbleToDismiss(bubble.getKey());
+ mSystemUiProxy.dragBubbleToDismiss(bubble.getKey(), mTimeSource.currentTimeMillis());
}
/**
@@ -601,6 +638,11 @@
void onBoundsChanged();
}
+ /** Interface for getting the current timestamp. */
+ interface TimeSource {
+ long currentTimeMillis();
+ }
+
/** Dumps the state of BubbleBarViewController. */
public void dump(PrintWriter pw) {
pw.println("Bubble bar view controller state:");
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 4c468bb..acb6b4e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar.bubbles;
import android.annotation.Nullable;
+import android.app.Notification;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -35,6 +36,7 @@
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
@@ -217,9 +219,9 @@
}
void updateDotVisibility(boolean animate) {
- final float targetScale = shouldDrawDot() ? 1f : 0f;
+ final float targetScale = hasUnseenContent() ? 1f : 0f;
if (animate) {
- animateDotScale();
+ animateDotScale(targetScale);
} else {
mDotScale = targetScale;
mAnimatingToDotScale = targetScale;
@@ -241,18 +243,27 @@
mAppIcon.setVisibility(show ? VISIBLE : GONE);
}
- /** Whether the dot indicating unseen content in a bubble should be shown. */
- private boolean shouldDrawDot() {
- boolean bubbleHasUnseenContent = mBubble != null
+ boolean hasUnseenContent() {
+ return mBubble != null
&& mBubble instanceof BubbleBarBubble
&& !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
- // Always render the dot if it's animating, since it could be animating out. Otherwise, show
- // it if the bubble wants to show it, and we aren't suppressing it.
- return bubbleHasUnseenContent || mDotIsAnimating;
}
- /** How big the dot should be, fraction from 0 to 1. */
- void setDotScale(float fraction) {
+ /**
+ * Used to determine if we can skip drawing frames.
+ *
+ * <p>Generally we should draw the dot when it is requested to be shown and there is unseen
+ * content. But when the dot is removed, we still want to draw frames so that it can be scaled
+ * out.
+ */
+ private boolean shouldDrawDot() {
+ // if there's no dot there's nothing to draw, unless the dot was removed and we're in the
+ // middle of removing it
+ return hasUnseenContent() || mDotIsAnimating;
+ }
+
+ /** Updates the dot scale to the specified fraction from 0 to 1. */
+ private void setDotScale(float fraction) {
if (!shouldDrawDot()) {
return;
}
@@ -260,11 +271,41 @@
invalidate();
}
- /**
- * Animates the dot to the given scale.
- */
- private void animateDotScale() {
- float toScale = shouldDrawDot() ? 1f : 0f;
+ void showDotIfNeeded(float fraction) {
+ if (!hasUnseenContent()) {
+ return;
+ }
+ setDotScale(fraction);
+ }
+
+ void showDotIfNeeded(boolean animate) {
+ // only show the dot if we have unseen content
+ if (!hasUnseenContent()) {
+ return;
+ }
+ if (animate) {
+ animateDotScale(1f);
+ } else {
+ setDotScale(1f);
+ }
+ }
+
+ void hideDot() {
+ animateDotScale(0f);
+ }
+
+ /** Marks this bubble such that it no longer has unseen content, and hides the dot. */
+ void markSeen() {
+ if (mBubble instanceof BubbleBarBubble bubble) {
+ BubbleInfo info = bubble.getInfo();
+ info.setFlags(
+ info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ hideDot();
+ }
+ }
+
+ /** Animates the dot to the given scale. */
+ private void animateDotScale(float toScale) {
boolean isDotScaleChanging = Float.compare(mDotScale, toScale) != 0;
// Don't restart the animation if we're already animating to the given value or if the dot
@@ -277,8 +318,6 @@
final boolean showDot = toScale > 0f;
- // Do NOT wait until after animation ends to setShowDot
- // to avoid overriding more recent showDot states.
clearAnimation();
animate()
.setDuration(200)
@@ -293,7 +332,6 @@
}).start();
}
-
@Override
public String toString() {
String toString = mBubble != null ? mBubble.getKey() : "null";
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/CustomizableTaskbarView.kt b/quickstep/src/com/android/launcher3/taskbar/customization/CustomizableTaskbarView.kt
new file mode 100644
index 0000000..e384586
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/CustomizableTaskbarView.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.customization
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.launcher3.Insettable
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.views.ActivityContext
+
+/** TaskbarView that is customizeable via Taskbar containers. */
+class CustomizableTaskbarView(context: Context, attrs: AttributeSet? = null) :
+ ConstraintLayout(context, attrs), Insettable {
+ private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+
+ init {
+ inflate(context, R.layout.customizable_taskbar_view, this)
+ }
+
+ override fun setInsets(insets: Rect?) {
+ // Ignore, we just implement Insettable to draw behind system insets.
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
new file mode 100644
index 0000000..415a051
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.customization
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.view.setPadding
+import com.android.launcher3.R
+import com.android.launcher3.Utilities.dpToPx
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.views.IconButtonView
+
+/** Taskbar all apps button container for customizable taskbar. */
+class TaskbarAllAppsButtonContainer
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : LinearLayout(context, attrs), TaskbarContainer {
+
+ private val allAppsButton: IconButtonView =
+ LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, this, false)
+ as IconButtonView
+ private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+
+ override val spaceNeeded: Int
+ get() {
+ return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
+ }
+
+ init {
+ setUpIcon()
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables", "ResourceAsColor")
+ private fun setUpIcon() {
+ val drawable =
+ resources.getDrawable(
+ getAllAppsButton(activityContext.taskbarFeatureEvaluator.isTransient)
+ )
+ val padding = activityContext.taskbarSpecsEvaluator.taskbarIconPadding
+
+ allAppsButton.setIconDrawable(drawable)
+ allAppsButton.setPadding(/* left= */ padding)
+ allAppsButton.setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
+
+ // TODO(jagrutdesai) : add click listeners in future cl
+ addView(allAppsButton)
+ }
+
+ @DrawableRes
+ private fun getAllAppsButton(isTransientTaskbar: Boolean): Int {
+ val shouldSelectTransientIcon =
+ isTransientTaskbar ||
+ (FeatureFlags.enableTaskbarPinning() && !activityContext.isThreeButtonNav)
+ return if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_search_button
+ else R.drawable.ic_taskbar_all_apps_search_button
+ } else {
+ if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_button
+ else R.drawable.ic_taskbar_all_apps_button
+ }
+ }
+
+ @DimenRes
+ fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int {
+ return if (isTransientTaskbar) {
+ R.dimen.transient_taskbar_all_apps_button_translation_x_offset
+ } else if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ R.dimen.taskbar_all_apps_search_button_translation_x_offset
+ } else {
+ R.dimen.taskbar_all_apps_button_translation_x_offset
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
index 3c4b63a..35ae43c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3.taskbar.customization
-/** Enums for all feature container that taskbar supports. */
-enum class TaskbarContainer {
- ALL_APPS,
- DIVIDER,
- APP_ICONS,
- RECENTS,
- NAV_BUTTONS,
- BUBBLES,
+import androidx.annotation.Dimension
+
+/**
+ * Interface to be implemented by all taskbar container to expose [spaceNeeded] for each container.
+ */
+interface TaskbarContainer {
+ @get:Dimension(unit = Dimension.DP) val spaceNeeded: Int
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainers.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainers.kt
new file mode 100644
index 0000000..d4548f5
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainers.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.customization
+
+/** Enums for all feature container that taskbar supports. */
+enum class TaskbarContainers {
+ ALL_APPS,
+ DIVIDER,
+ APP_ICONS,
+ RECENTS,
+ NAV_BUTTONS,
+ BUBBLES,
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
index ac7dd06..c83ac50 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
@@ -16,27 +16,43 @@
package com.android.launcher3.taskbar.customization
+import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.taskbar.TaskbarControllers
import com.android.launcher3.util.DisplayController
/** Evaluates all the features taskbar can have. */
-class TaskbarFeatureEvaluator(
+class TaskbarFeatureEvaluator
+private constructor(
private val taskbarActivityContext: TaskbarActivityContext,
- private val taskbarControllers: TaskbarControllers,
) {
+ companion object {
+ @Volatile private lateinit var taskbarFeatureEvaluator: TaskbarFeatureEvaluator
+
+ @JvmStatic
+ fun getInstance(
+ taskbarActivityContext: TaskbarActivityContext,
+ ): TaskbarFeatureEvaluator {
+ synchronized(this) {
+ if (!::taskbarFeatureEvaluator.isInitialized) {
+ taskbarFeatureEvaluator = TaskbarFeatureEvaluator(taskbarActivityContext)
+ }
+ return taskbarFeatureEvaluator
+ }
+ }
+ }
+
val hasAllApps = true
val hasAppIcons = true
val hasBubbles = false
val hasNavButtons = taskbarActivityContext.isThreeButtonNav
- val hasRecents: Boolean
- get() = taskbarControllers.taskbarRecentAppsController.shownTasks.isNotEmpty()
+ val isRecentsEnabled: Boolean
+ get() = enableRecentsInTaskbar()
val hasDivider: Boolean
- get() = enableTaskbarPinning() || hasRecents
+ get() = enableTaskbarPinning() || isRecentsEnabled
val isTransient: Boolean
get() = DisplayController.isTransientTaskbar(taskbarActivityContext)
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
index 67bbcce..887eb01 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -29,6 +29,13 @@
val defaultPersistentIconSize = iconSize40dp
val defaultTransientIconSize = iconSize44dp
+ val minimumIconSize = iconSize40dp
+
+ val defaultPersistentIconMargin = TaskbarIconMarginSize(6)
+ val defaultTransientIconMargin = TaskbarIconMarginSize(12)
+
+ val minimumTaskbarIconTouchSize = TaskbarIconSize(48)
+
val transientTaskbarIconSizeByGridSize =
mapOf(
TransientTaskbarIconSizeKey(6, 5, false) to iconSize52dp,
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
index 0b7be40..761b47e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -16,8 +16,33 @@
package com.android.launcher3.taskbar.customization
+import com.android.launcher3.taskbar.TaskbarActivityContext
+
/** Evaluates the taskbar specs based on the taskbar grid size and the taskbar icon size. */
-class TaskbarSpecsEvaluator(private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator) {
+class TaskbarSpecsEvaluator(
+ private val taskbarActivityContext: TaskbarActivityContext,
+ private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator,
+ numRows: Int = taskbarActivityContext.deviceProfile.inv.numRows,
+ numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
+) {
+ var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numRows, numColumns)
+
+ // TODO(b/341146605) : initialize it to taskbar container in later cl.
+ private var taskbarContainer: List<TaskbarContainer> = emptyList()
+
+ val taskbarIconPadding: Int =
+ if (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size > taskbarIconSize.size) {
+ (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size - taskbarIconSize.size) / 2
+ } else {
+ 0
+ }
+
+ val taskbarIconMargin: TaskbarIconMarginSize =
+ if (taskbarFeatureEvaluator.isTransient) {
+ TaskbarIconSpecs.defaultTransientIconMargin
+ } else {
+ TaskbarIconSpecs.defaultPersistentIconMargin
+ }
fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
return if (taskbarFeatureEvaluator.isTransient) {
@@ -36,8 +61,11 @@
val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
// return the current icon size if supplied icon size is unknown or we have reached the
// min icon size.
- return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) iconSize
- else TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
+ return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) {
+ iconSize
+ } else {
+ TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
+ }
}
fun getIconSizeStepUp(iconSize: TaskbarIconSize): TaskbarIconSize {
@@ -52,11 +80,28 @@
) {
iconSize
} else {
- TaskbarIconSpecs.transientTaskbarIconSizes.get(currentIconSizeIndex + 1)
+ TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex + 1]
}
}
+
+ // TODO(jagrutdesai) : Call this in init once the containers are ready.
+ private fun calculateTaskbarIconSize() {
+ while (
+ taskbarIconSize != TaskbarIconSpecs.minimumIconSize &&
+ taskbarActivityContext.transientTaskbarBounds.width() <
+ calculateSpaceNeeded(taskbarContainer)
+ ) {
+ taskbarIconSize = getIconSizeStepDown(taskbarIconSize)
+ }
+ }
+
+ private fun calculateSpaceNeeded(containers: List<TaskbarContainer>): Int {
+ return containers.sumOf { it.spaceNeeded }
+ }
}
data class TaskbarIconSize(val size: Int)
data class TransientTaskbarIconSizeKey(val row: Int, val column: Int, val isLandscape: Boolean)
+
+data class TaskbarIconMarginSize(val size: Int)
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f020c8f..20eaddc 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1631,14 +1631,17 @@
mRecentsAnimationController.screenshotTask(taskId));
});
- // let SystemUi reparent the overlay leash as soon as possible
+ // let SystemUi reparent the overlay leash as soon as possible;
+ // make sure to pass in an empty src-rect-hint if overlay is present, since we
+ // use our own calculated source-rect-hint for the animation.
SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
mSwipePipToHomeAnimator.getTaskId(),
mSwipePipToHomeAnimator.getComponentName(),
mSwipePipToHomeAnimator.getDestinationBounds(),
mSwipePipToHomeAnimator.getContentOverlay(),
mSwipePipToHomeAnimator.getAppBounds(),
- mSwipePipToHomeAnimator.getSourceRectHint());
+ mSwipePipToHomeAnimator.getContentOverlay() != null ? new Rect()
+ : mSwipePipToHomeAnimator.getSourceRectHint());
windowAnim = mSwipePipToHomeAnimators;
} else {
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
index 3549a12..904ed69 100644
--- a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -64,11 +64,19 @@
"Enable two stage for LPNH duration and touch slop"
)
- val twoStageMultiplier =
+ val twoStageDurationPercentage =
propReader.get(
- "TWO_STAGE_MULTIPLIER",
- 2,
- "Extends the duration and touch slop if the initial slop is passed"
+ "TWO_STAGE_DURATION_PERCENTAGE",
+ 200,
+ "Extends the duration to trigger a long press after a fraction of the gesture " +
+ "slop is passed, expressed as a percentage (i.e. 200 = 2x)."
+ )
+
+ val twoStageSlopPercentage =
+ propReader.get(
+ "TWO_STAGE_SLOP_PERCENTAGE",
+ 50,
+ "Percentage of gesture slop region to trigger the extended long press duration."
)
val animateLpnh = propReader.get("ANIMATE_LPNH", false, "Animates navbar when long pressing")
diff --git a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
index 27bd03d..7d22c52 100644
--- a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
+++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
@@ -5,6 +5,7 @@
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
import android.content.Context
+import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags.enableLauncherBrMetricsFixed
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
@@ -29,8 +30,8 @@
@BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair"
}
- private val restoreEventLogger: BackupRestoreEventLogger =
- BackupManager(context).delayedRestoreLogger
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ val restoreEventLogger: BackupRestoreEventLogger = BackupManager(context).delayedRestoreLogger
/**
* For logging when multiple items of a given data type failed to restore.
diff --git a/quickstep/src/com/android/quickstep/OrientationRectF.java b/quickstep/src/com/android/quickstep/OrientationRectF.java
index aa01b05..2b7ecb2 100644
--- a/quickstep/src/com/android/quickstep/OrientationRectF.java
+++ b/quickstep/src/com/android/quickstep/OrientationRectF.java
@@ -67,13 +67,15 @@
}
public boolean applyTransform(MotionEvent event, int deltaRotation, boolean forceTransform) {
+ if (deltaRotation == 0) {
+ return contains(event.getX(), event.getY());
+ }
mTmpMatrix.reset();
postDisplayRotation(deltaRotation, mHeight, mWidth, mTmpMatrix);
if (forceTransform) {
if (DEBUG) {
Log.d(TAG, "Transforming rotation due to forceTransform, "
+ "deltaRotation: " + deltaRotation
- + "mRotation: " + mRotation
+ " this: " + this);
}
event.applyTransform(mTmpMatrix);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 3d4167a..4989831 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -300,8 +300,12 @@
@VisibleForTesting
TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
int currentUserId = Process.myUserHandle().getIdentifier();
- ArrayList<GroupedRecentTaskInfo> rawTasks =
- mSysUiProxy.getRecentTasks(numTasks, currentUserId);
+ ArrayList<GroupedRecentTaskInfo> rawTasks;
+ try {
+ rawTasks = mSysUiProxy.getRecentTasks(numTasks, currentUserId);
+ } catch (SystemUiProxy.GetRecentTasksException e) {
+ return INVALID_RESULT;
+ }
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(rawTasks);
@@ -416,8 +420,12 @@
}
writer.println(prefix + " ]");
int currentUserId = Process.myUserHandle().getIdentifier();
- ArrayList<GroupedRecentTaskInfo> rawTasks =
- mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+ ArrayList<GroupedRecentTaskInfo> rawTasks;
+ try {
+ rawTasks = mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+ } catch (SystemUiProxy.GetRecentTasksException e) {
+ rawTasks = new ArrayList<>();
+ }
writer.println(prefix + " rawTasks=[");
for (GroupedRecentTaskInfo task : rawTasks) {
TaskInfo taskInfo1 = task.getTaskInfo1();
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index b796951..f2b6005 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -18,6 +18,7 @@
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
@@ -108,7 +109,7 @@
mIconCache = iconCache;
mIconCache.registerTaskVisualsChangeListener(this);
mThumbnailCache = thumbnailCache;
- if (enableGridOnlyOverview()) {
+ if (isCachePreloadingEnabled()) {
mCallbacks = new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
@@ -342,7 +343,7 @@
* highResLoadingState is enabled
*/
public void preloadCacheIfNeeded() {
- if (!enableGridOnlyOverview()) {
+ if (!isCachePreloadingEnabled()) {
return;
}
@@ -368,7 +369,7 @@
* Updates cache size and preloads more tasks if cache size increases
*/
public void updateCacheSizeAndPreloadIfNeeded() {
- if (!enableGridOnlyOverview()) {
+ if (!isCachePreloadingEnabled()) {
return;
}
@@ -387,6 +388,10 @@
mTaskStackChangeListeners.unregisterTaskStackListener(this);
}
+ private boolean isCachePreloadingEnabled() {
+ return enableGridOnlyOverview() || enableRefactorTaskThumbnail();
+ }
+
/**
* Listener for receiving running tasks changes
*/
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index 29a57fc..5264643 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -34,12 +34,18 @@
private final Context mContext;
private OrientationRectF mOrientationRectF;
+ private OrientationRectF mTouchingOrientationRectF;
+ private int mViewRotation;
public SimpleOrientationTouchTransformer(Context context) {
+ this(context, DisplayController.INSTANCE.get(context));
+ }
+
+ @androidx.annotation.VisibleForTesting
+ public SimpleOrientationTouchTransformer(Context context, DisplayController displayController) {
mContext = context;
- DisplayController.INSTANCE.get(context).addChangeListener(this);
- onDisplayInfoChanged(context, DisplayController.INSTANCE.get(context).getInfo(),
- CHANGE_ALL);
+ displayController.addChangeListener(this);
+ onDisplayInfoChanged(context, displayController.getInfo(), CHANGE_ALL);
}
@Override
@@ -56,7 +62,29 @@
info.rotation);
}
+ /**
+ * Called when the touch is started. This preserves the touching orientation until the touch is
+ * done (i.e. ACTION_CANCEL or ACTION_UP). So the transform won't produce inconsistent position
+ * if display is changed during the touch.
+ */
+ public void updateTouchingOrientation(int viewRotation) {
+ mViewRotation = viewRotation;
+ mTouchingOrientationRectF = new OrientationRectF(mOrientationRectF.left,
+ mOrientationRectF.top, mOrientationRectF.right, mOrientationRectF.bottom,
+ mOrientationRectF.getRotation());
+ }
+
+ /** Called when the touch is finished. */
+ public void clearTouchingOrientation() {
+ mTouchingOrientationRectF = null;
+ }
+
public void transform(MotionEvent ev, int rotation) {
+ if (mTouchingOrientationRectF != null) {
+ mTouchingOrientationRectF.applyTransformToRotation(ev, mViewRotation,
+ true /* forceTransform */);
+ return;
+ }
mOrientationRectF.applyTransformToRotation(ev, rotation, true /* forceTransform */);
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 0d9f81f..3f73959 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -842,12 +842,14 @@
/**
* Tells SysUI to dismiss the bubble with the provided key.
+ *
* @param key the key of the bubble to dismiss.
+ * @param timestamp the timestamp when the removal happened.
*/
- public void dragBubbleToDismiss(String key) {
+ public void dragBubbleToDismiss(String key, long timestamp) {
if (mBubbles == null) return;
try {
- mBubbles.dragBubbleToDismiss(key);
+ mBubbles.dragBubbleToDismiss(key, timestamp);
} catch (RemoteException e) {
Log.w(TAG, "Failed call dragBubbleToDismiss");
}
@@ -1392,10 +1394,26 @@
}
}
- public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
+ public static class GetRecentTasksException extends Exception {
+ public GetRecentTasksException(String message) {
+ super(message);
+ }
+
+ public GetRecentTasksException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Retrieves a list of Recent tasks from ActivityManager.
+ * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get
+ * RemoteException from server side
+ */
+ public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId)
+ throws GetRecentTasksException {
if (mRecentTasks == null) {
- Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks");
- return new ArrayList<>();
+ Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks");
+ throw new GetRecentTasksException("null mRecentTasks");
}
try {
final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,
@@ -1405,8 +1423,8 @@
}
return new ArrayList<>(Arrays.asList(rawTasks));
} catch (RemoteException e) {
- Log.w(TAG, "Failed call getRecentTasks", e);
- return new ArrayList<>();
+ Log.e(TAG, "Failed call getRecentTasks", e);
+ throw new GetRecentTasksException("Failed call getRecentTasks", e);
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 08bb6cd..723aa03 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -332,7 +332,8 @@
if (ENABLE_SHELL_TRANSITIONS) {
final ActivityOptions options = ActivityOptions.makeBasic();
- options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
// Use regular (non-transient) launch for all apps page to control IME.
if (!containerInterface.allowAllAppsFromOverview()) {
options.setTransientLaunch();
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 80902e3..f4ff4b2 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -184,6 +184,10 @@
return mTaskContainer.getTaskView();
}
+ public View getSnapshotView() {
+ return mTaskContainer.getSnapshotView();
+ }
+
/**
* Called when the current task is interactive for the user
*/
@@ -312,7 +316,7 @@
// the difference between the bitmap bounds and the projected view bounds.
Matrix boundsToBitmapSpace = new Matrix();
Matrix thumbnailMatrix = enableRefactorTaskThumbnail()
- ? mHelper.getEnabledState().getThumbnailMatrix()
+ ? mHelper.getThumbnailMatrix()
: mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix();
thumbnailMatrix.invert(boundsToBitmapSpace);
RectF boundsInBitmapSpace = new RectF();
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 77124bf..dad34ac 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -23,7 +23,6 @@
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.app.ActivityOptions;
import android.graphics.Bitmap;
@@ -63,6 +62,7 @@
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.util.Collections;
import java.util.List;
@@ -394,7 +394,7 @@
return Settings.Global.getInt(
container.asContext().getContentResolver(),
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0
- && !enableDesktopWindowingMode();
+ && !DesktopModeStatus.canEnterDesktopMode(container.asContext());
}
};
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index 848a43a..186c453 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -73,17 +73,32 @@
super(delegate, inputMonitor);
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
- int twoStageMultiplier = DeviceConfigWrapper.get().getTwoStageMultiplier();
AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(context);
if (assistStateManager.getLPNHDurationMillis().isPresent()) {
mLongPressTimeout = assistStateManager.getLPNHDurationMillis().get().intValue();
} else {
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
- mOuterLongPressTimeout = mLongPressTimeout * twoStageMultiplier;
- mTouchSlopSquaredOriginal = deviceState.getSquaredTouchSlop();
- mTouchSlopSquared = mTouchSlopSquaredOriginal;
- mOuterTouchSlopSquared = mTouchSlopSquared * (twoStageMultiplier * twoStageMultiplier);
+ float twoStageDurationMultiplier =
+ (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
+ mOuterLongPressTimeout = (int) (mLongPressTimeout * twoStageDurationMultiplier);
+
+ float gestureNavTouchSlopSquared = deviceState.getSquaredTouchSlop();
+ float twoStageSlopMultiplier =
+ (DeviceConfigWrapper.get().getTwoStageSlopPercentage() / 100f);
+ float twoStageSlopMultiplierSquared = twoStageSlopMultiplier * twoStageSlopMultiplier;
+ if (DeviceConfigWrapper.get().getEnableLpnhTwoStages()) {
+ // For 2 stages, the outer touch slop should match gesture nav.
+ mTouchSlopSquared = gestureNavTouchSlopSquared * twoStageSlopMultiplierSquared;
+ mOuterTouchSlopSquared = gestureNavTouchSlopSquared;
+ } else {
+ // For single stage, the touch slop should match gesture nav.
+ mTouchSlopSquared = gestureNavTouchSlopSquared;
+ // Note: This outer slop is not actually used for single-stage (flag disabled).
+ mOuterTouchSlopSquared = gestureNavTouchSlopSquared;
+ }
+ mTouchSlopSquaredOriginal = mTouchSlopSquared;
+
mGestureState = gestureState;
mGestureState.setIsInExtendedSlopRegion(false);
if (DEBUG_NAV_HANDLE) {
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 5ac04da..717f6c8 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -39,7 +39,8 @@
import android.util.Log;
import android.util.Xml;
-import com.android.launcher3.AutoInstallsLayout;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.logging.InstanceId;
@@ -73,7 +74,6 @@
new MainThreadInitializedObject<>(SettingsChangeLogger::new);
private static final String TAG = "SettingsChangeLogger";
- private static final String ROOT_TAG = "androidx.preference.PreferenceScreen";
private static final String BOOLEAN_PREF = "SwitchPreference";
private final Context mContext;
@@ -85,8 +85,13 @@
private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent;
private SettingsChangeLogger(Context context) {
+ this(context, StatsLogManager.newInstance(context));
+ }
+
+ @VisibleForTesting
+ SettingsChangeLogger(Context context, StatsLogManager statsLogManager) {
mContext = context;
- mStatsLogManager = StatsLogManager.newInstance(mContext);
+ mStatsLogManager = statsLogManager;
mLoggablePrefs = loadPrefKeys(context);
DisplayController.INSTANCE.get(context).addChangeListener(this);
mNavMode = DisplayController.getNavigationMode(context);
@@ -105,7 +110,13 @@
ArrayMap<String, LoggablePref> result = new ArrayMap<>();
try {
- AutoInstallsLayout.beginDocument(parser, ROOT_TAG);
+ // Move cursor to first tag because it could be
+ // androidx.preference.PreferenceScreen or PreferenceScreen
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG
@@ -189,13 +200,19 @@
prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff));
}
+ @VisibleForTesting
+ ArrayMap<String, LoggablePref> getLoggingPrefs() {
+ return mLoggablePrefs;
+ }
+
@Override
public void close() {
getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
}
- private static class LoggablePref {
+ @VisibleForTesting
+ static class LoggablePref {
public boolean defaultValue;
public int eventIdOn;
public int eventIdOff;
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
new file mode 100644
index 0000000..adf904c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.quickstep.views.RecentsViewContainer
+
+/**
+ * Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
+ * data related to Recents.
+ */
+class RecentsDeviceProfileRepository(private val container: RecentsViewContainer) {
+
+ fun getRecentsDeviceProfile() =
+ with(container.deviceProfile) { RecentsDeviceProfile(isLargeScreen = isTablet) }
+
+ /**
+ * Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
+ *
+ * @property isLargeScreen whether the current device posture has a large screen
+ */
+ data class RecentsDeviceProfile(val isLargeScreen: Boolean)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
new file mode 100644
index 0000000..6ead704
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.quickstep.util.RecentsOrientedState
+
+/**
+ * Repository for [RecentsRotationState] which holds orientation/rotation related information
+ * related to Recents
+ */
+class RecentsRotationStateRepository(private val state: RecentsOrientedState) {
+ fun getRecentsRotationState() =
+ with(state) { RecentsRotationState(activityRotation = recentsActivityRotation) }
+
+ /**
+ * Container to hold orientation/rotation related information related to Recents.
+ *
+ * @property activityRotation rotation of the activity hosting RecentsView
+ */
+ data class RecentsRotationState(val activityRotation: Int)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
new file mode 100644
index 0000000..7a5a714
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.di
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.TasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.GetThumbnailUseCase
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.task.viewmodel.TaskViewModel
+import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.Task
+import java.util.logging.Level
+
+internal typealias RecentsScopeId = String
+
+class RecentsDependencies private constructor(private val appContext: Context) {
+ private val scopes = mutableMapOf<RecentsScopeId, RecentsDependenciesScope>()
+
+ init {
+ startDefaultScope(appContext)
+ }
+
+ /**
+ * This function initialised the default scope with RecentsView dependencies. These dependencies
+ * are used multiple times and should be a singleton to share across Recents classes.
+ */
+ private fun startDefaultScope(appContext: Context) {
+ createScope(DEFAULT_SCOPE_ID).apply {
+ set(RecentsViewData::class.java.simpleName, RecentsViewData())
+
+ // Create RecentsTaskRepository singleton
+ val recentTasksRepository: RecentTasksRepository =
+ with(RecentsModel.INSTANCE.get(appContext)) {
+ TasksRepository(this, thumbnailCache, iconCache)
+ }
+ set(RecentTasksRepository::class.java.simpleName, recentTasksRepository)
+ }
+ }
+
+ inline fun <reified T> inject(
+ scopeId: RecentsScopeId = "",
+ extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
+ noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+ ): T = inject(T::class.java, scopeId = scopeId, extras = extras, factory = factory)
+
+ @Suppress("UNCHECKED_CAST")
+ @JvmOverloads
+ fun <T> inject(
+ modelClass: Class<T>,
+ scopeId: RecentsScopeId = DEFAULT_SCOPE_ID,
+ extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
+ factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+ ): T {
+ val currentScopeId = scopeId.ifEmpty { DEFAULT_SCOPE_ID }
+ val scope = scopes[currentScopeId] ?: createScope(currentScopeId)
+
+ log("inject ${modelClass.simpleName} into ${scope.scopeId}", Log.INFO)
+ var instance: T?
+ synchronized(this) {
+ instance = getDependency(scope, modelClass)
+ log("found instance? $instance", Log.INFO)
+ if (instance == null) {
+ instance =
+ factory?.invoke(extras) as T ?: createDependency(modelClass, scopeId, extras)
+ scope[modelClass.simpleName] = instance!!
+ }
+ }
+ return instance!!
+ }
+
+ inline fun <reified T> provide(scopeId: RecentsScopeId = "", noinline factory: () -> T): T =
+ provide(T::class.java, scopeId = scopeId, factory = factory)
+
+ @JvmOverloads
+ fun <T> provide(
+ modelClass: Class<T>,
+ scopeId: RecentsScopeId = DEFAULT_SCOPE_ID,
+ factory: () -> T,
+ ) = inject(modelClass, scopeId, factory = { factory.invoke() })
+
+ private fun <T> getDependency(scope: RecentsDependenciesScope, modelClass: Class<T>): T? {
+ var instance: T? = scope[modelClass.simpleName] as T?
+ if (instance == null) {
+ instance =
+ scope.scopeIdsLinked.firstNotNullOfOrNull { scopeId ->
+ getScope(scopeId)[modelClass.simpleName]
+ } as T?
+ }
+ if (instance != null) log("Found dependency: $instance", Log.INFO)
+ return instance
+ }
+
+ fun getScope(scope: Any): RecentsDependenciesScope {
+ val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString()
+ return getScope(scopeId)
+ }
+
+ fun getScope(scopeId: RecentsScopeId): RecentsDependenciesScope =
+ scopes[scopeId] ?: createScope(scopeId)
+
+ // TODO(b/353912757): Create a factory so we can prevent this method of growing indefinitely.
+ // Each class should be responsible for providing a factory function to create a new instance.
+ @Suppress("UNCHECKED_CAST")
+ private fun <T> createDependency(
+ modelClass: Class<T>,
+ scopeId: RecentsScopeId,
+ extras: RecentsDependenciesExtras,
+ ): T {
+ log("createDependency ${modelClass.simpleName} with $scopeId and $extras", Log.WARN)
+ val instance: Any =
+ when (modelClass) {
+ RecentTasksRepository::class.java -> {
+ with(RecentsModel.INSTANCE.get(appContext)) {
+ TasksRepository(this, thumbnailCache, iconCache)
+ }
+ }
+ RecentsViewData::class.java -> RecentsViewData()
+ TaskViewModel::class.java -> TaskViewModel(taskViewData = inject(scopeId, extras))
+ TaskViewData::class.java -> {
+ val taskViewType = extras["TaskViewType"] as TaskViewType
+ TaskViewData(taskViewType)
+ }
+ TaskContainerData::class.java -> TaskContainerData()
+ TaskThumbnailViewModel::class.java ->
+ TaskThumbnailViewModel(
+ recentsViewData = inject(),
+ taskViewData = inject(scopeId, extras),
+ taskContainerData = inject(),
+ getThumbnailPositionUseCase = inject(),
+ tasksRepository = inject()
+ )
+ TaskOverlayViewModel::class.java -> {
+ val task = extras["Task"] as Task
+ TaskOverlayViewModel(
+ task = task,
+ recentsViewData = inject(),
+ recentTasksRepository = inject(),
+ getThumbnailPositionUseCase = inject()
+ )
+ }
+ GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
+ GetThumbnailPositionUseCase::class.java ->
+ GetThumbnailPositionUseCase(
+ deviceProfileRepository = inject(),
+ rotationStateRepository = inject(),
+ tasksRepository = inject()
+ )
+ else -> {
+ log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR)
+ error("Factory for ${modelClass.simpleName} not defined!")
+ }
+ }
+ return instance as T
+ }
+
+ private fun createScope(scopeId: RecentsScopeId): RecentsDependenciesScope {
+ return RecentsDependenciesScope(scopeId).also { scopes[scopeId] = it }
+ }
+
+ private fun log(message: String, @Log.Level level: Int = Log.DEBUG) {
+ if (DEBUG) {
+ when (level) {
+ Log.WARN -> Log.w(TAG, message)
+ Log.VERBOSE -> Log.v(TAG, message)
+ Log.INFO -> Log.i(TAG, message)
+ Log.ERROR -> Log.e(TAG, message)
+ else -> Log.d(TAG, message)
+ }
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_SCOPE_ID = "RecentsDependencies::GlobalScope"
+ private const val TAG = "RecentsDependencies"
+ private const val DEBUG = false
+
+ @Volatile private lateinit var instance: RecentsDependencies
+
+ fun initialize(view: View): RecentsDependencies = initialize(view.context)
+
+ fun initialize(context: Context): RecentsDependencies {
+ synchronized(this) {
+ if (!Companion::instance.isInitialized) {
+ instance = RecentsDependencies(context.applicationContext)
+ }
+ }
+ return instance
+ }
+
+ fun getInstance(): RecentsDependencies {
+ if (!Companion::instance.isInitialized) {
+ throw UninitializedPropertyAccessException(
+ "Recents dependencies are not initialized. " +
+ "Call `RecentsDependencies.initialize` before using this container."
+ )
+ }
+ return instance
+ }
+
+ fun destroy() {
+ instance.scopes.clear()
+ instance.startDefaultScope(instance.appContext)
+ }
+ }
+}
+
+inline fun <reified T> RecentsDependencies.Companion.inject(
+ scope: Any = "",
+ vararg extras: Pair<String, Any>,
+ noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+): Lazy<T> = lazy { get(scope, RecentsDependenciesExtras(extras), factory) }
+
+inline fun <reified T> RecentsDependencies.Companion.get(
+ scope: Any = "",
+ extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
+ noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+): T {
+ val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString()
+ return getInstance().inject(scopeId, extras, factory)
+}
+
+inline fun <reified T> RecentsDependencies.Companion.get(
+ scope: Any = "",
+ vararg extras: Pair<String, Any>,
+ noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+): T = get(scope, RecentsDependenciesExtras(extras), factory)
+
+fun RecentsDependencies.Companion.getScope(scopeId: Any) = getInstance().getScope(scopeId)
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt
new file mode 100644
index 0000000..753cb6e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.di
+
+data class RecentsDependenciesExtras(private val data: MutableMap<String, Any> = mutableMapOf()) {
+ constructor(value: Array<out Pair<String, Any>>) : this(value.toMap().toMutableMap())
+
+ operator fun get(key: String) = data[key]
+
+ operator fun set(key: String, value: Any) {
+ data[key] = value
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt
new file mode 100644
index 0000000..56bb1ed
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.di
+
+import android.util.Log
+
+class RecentsDependenciesScope(
+ val scopeId: RecentsScopeId,
+ private val dependencies: MutableMap<String, Any> = mutableMapOf(),
+ private val scopeIds: MutableList<RecentsScopeId> = mutableListOf()
+) {
+ val scopeIdsLinked: List<RecentsScopeId>
+ get() = scopeIds.toList()
+
+ operator fun get(identifier: String): Any? {
+ log("get $identifier")
+ return dependencies[identifier]
+ }
+
+ operator fun set(key: String, value: Any) {
+ synchronized(this) {
+ log("set $key")
+ dependencies[key] = value
+ }
+ }
+
+ fun remove(key: String): Any? {
+ synchronized(this) {
+ log("remove $key")
+ return dependencies.remove(key)
+ }
+ }
+
+ fun linkTo(scope: RecentsDependenciesScope) {
+ log("linking to ${scope.scopeId}")
+ scopeIds += scope.scopeId
+ }
+
+ fun close() {
+ log("reset")
+ synchronized(this) { dependencies.clear() }
+ }
+
+ private fun log(message: String) {
+ if (DEBUG) Log.d(TAG, "[scopeId=$scopeId] $message")
+ }
+
+ override fun toString(): String =
+ "scopeId: $scopeId" +
+ "\n dependencies: ${dependencies.map { "${it.key}=${it.value}" }.joinToString(", ")}" +
+ "\n linked to: ${scopeIds.joinToString(", ")}"
+
+ private companion object {
+ private const val TAG = "RecentsDependenciesScope"
+ private const val DEBUG = false
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
new file mode 100644
index 0000000..f060d7d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.usecase
+
+import android.graphics.Matrix
+import android.graphics.Rect
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import kotlinx.coroutines.flow.firstOrNull
+
+/** Use case for retrieving [Matrix] for positioning Thumbnail in a View */
+class GetThumbnailPositionUseCase(
+ private val deviceProfileRepository: RecentsDeviceProfileRepository,
+ private val rotationStateRepository: RecentsRotationStateRepository,
+ private val tasksRepository: RecentTasksRepository,
+ private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper()
+) {
+ suspend fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
+ val thumbnailData =
+ tasksRepository.getThumbnailById(taskId).firstOrNull() ?: return MissingThumbnail
+ val thumbnail = thumbnailData.thumbnail ?: return MissingThumbnail
+ previewPositionHelper.updateThumbnailMatrix(
+ Rect(0, 0, thumbnail.width, thumbnail.height),
+ thumbnailData,
+ width,
+ height,
+ deviceProfileRepository.getRecentsDeviceProfile().isLargeScreen,
+ rotationStateRepository.getRecentsRotationState().activityRotation,
+ isRtl
+ )
+ return MatrixScaling(
+ previewPositionHelper.matrix,
+ previewPositionHelper.isOrientationChanged
+ )
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
similarity index 95%
rename from quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
rename to quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
index e8dd04c3..3aa808e 100644
--- a/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep.task.util
+package com.android.quickstep.recents.usecase
import android.graphics.Bitmap
import com.android.quickstep.recents.data.RecentTasksRepository
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt b/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt
new file mode 100644
index 0000000..1a1bef7
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.usecase
+
+import android.graphics.Matrix
+
+/** State on how a task Thumbnail can fit on given canvas */
+sealed class ThumbnailPositionState {
+ data object MissingThumbnail : ThumbnailPositionState()
+
+ data class MatrixScaling(val matrix: Matrix, val isRotated: Boolean) : ThumbnailPositionState()
+}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
new file mode 100644
index 0000000..8b03a84
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.viewmodel
+
+import com.android.quickstep.recents.data.RecentTasksRepository
+
+class RecentsViewModel(
+ private val recentsTasksRepository: RecentTasksRepository,
+ private val recentsViewData: RecentsViewData
+) {
+ fun refreshAllTaskData() {
+ recentsTasksRepository.getAllTaskData(true)
+ }
+
+ fun updateVisibleTasks(visibleTaskIdList: List<Int>) {
+ recentsTasksRepository.setVisibleTasks(visibleTaskIdList)
+ }
+
+ fun updateScale(scale: Float) {
+ recentsViewData.scale.value = scale
+ }
+
+ fun updateFullscreenProgress(fullscreenProgress: Float) {
+ recentsViewData.fullscreenProgress.value = fullscreenProgress
+ }
+
+ fun updateTasksFullyVisible(taskIds: Set<Int>) {
+ recentsViewData.settledFullyVisibleTaskIds.value = taskIds
+ }
+
+ fun setOverlayEnabled(isOverlayEnabled: Boolean) {
+ recentsViewData.overlayEnabled.value = isOverlayEnabled
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
index feee11f..5fb5b90 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
@@ -17,15 +17,10 @@
package com.android.quickstep.task.thumbnail
import android.graphics.Bitmap
-import android.graphics.Matrix
/** Ui state for [com.android.quickstep.TaskOverlayFactory.TaskOverlay] */
sealed class TaskOverlayUiState {
data object Disabled : TaskOverlayUiState()
- data class Enabled(
- val isRealSnapshot: Boolean,
- val thumbnail: Bitmap?,
- val thumbnailMatrix: Matrix
- ) : TaskOverlayUiState()
+ data class Enabled(val isRealSnapshot: Boolean, val thumbnail: Bitmap?) : TaskOverlayUiState()
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index 40f9b28..3b3a811 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -17,18 +17,17 @@
package com.android.quickstep.task.thumbnail
import android.graphics.Bitmap
-import android.graphics.Rect
import androidx.annotation.ColorInt
sealed class TaskThumbnailUiState {
data object Uninitialized : TaskThumbnailUiState()
+
data object LiveTile : TaskThumbnailUiState()
+
data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
- data class Snapshot(
- val bitmap: Bitmap,
- val drawnRect: Rect,
- @ColorInt val backgroundColor: Int
- ) : TaskThumbnailUiState()
+
+ data class Snapshot(val bitmap: Bitmap, @ColorInt val backgroundColor: Int) :
+ TaskThumbnailUiState()
}
data class TaskThumbnail(val taskId: Int, val isRunning: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index d22fc94..fcc2af3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -31,14 +31,14 @@
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.util.ViewPool
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.inject
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.quickstep.util.TaskCornerRadius
-import com.android.quickstep.views.RecentsView
-import com.android.quickstep.views.RecentsViewContainer
-import com.android.quickstep.views.TaskView
import com.android.systemui.shared.system.QuickStepContract
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
@@ -49,26 +49,16 @@
import kotlinx.coroutines.flow.onEach
class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
- // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
- // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
- // This is using a lazy for now because the dependencies cannot be obtained without DI.
- val viewModel by lazy {
- val recentsView =
- RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
- .getOverviewPanel<RecentsView<*, *>>()
- TaskThumbnailViewModel(
- recentsView.mRecentsViewData!!,
- (parent as TaskView).taskViewData,
- (parent as TaskView).getTaskContainerForTaskThumbnailView(this)!!.taskContainerData,
- recentsView.mTasksRepository!!,
- )
- }
+
+ private val viewModel: TaskThumbnailViewModel by RecentsDependencies.inject(this)
+
private lateinit var viewAttachedScope: CoroutineScope
private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
- private val thumbnail: ImageView by lazy { findViewById(R.id.task_thumbnail) }
+ private val thumbnailView: ImageView by lazy { findViewById(R.id.task_thumbnail) }
+ private var uiState: TaskThumbnailUiState = Uninitialized
private var inheritedScale: Float = 1f
private val _measuredBounds = Rect()
@@ -97,6 +87,7 @@
CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskThumbnailView"))
viewModel.uiState
.onEach { viewModelUiState ->
+ uiState = viewModelUiState
resetViews()
when (viewModelUiState) {
is Uninitialized -> {}
@@ -135,7 +126,14 @@
}
override fun onRecycle() {
- // Do nothing
+ uiState = Uninitialized
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ if (uiState is Snapshot) {
+ setImageMatrix()
+ }
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -148,7 +146,7 @@
private fun resetViews() {
liveTileView.isVisible = false
- thumbnail.isVisible = false
+ thumbnailView.isVisible = false
scrimView.alpha = 0f
setBackgroundColor(Color.BLACK)
}
@@ -163,8 +161,13 @@
private fun drawSnapshot(snapshot: Snapshot) {
drawBackground(snapshot.backgroundColor)
- thumbnail.setImageBitmap(snapshot.bitmap)
- thumbnail.isVisible = true
+ thumbnailView.setImageBitmap(snapshot.bitmap)
+ thumbnailView.isVisible = true
+ setImageMatrix()
+ }
+
+ private fun setImageMatrix() {
+ thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
}
private fun getCurrentCornerRadius() =
diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
index 5e55e2e..9253dbf 100644
--- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
+++ b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
@@ -18,70 +18,85 @@
import android.util.Log
import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
import com.android.quickstep.task.thumbnail.TaskOverlayUiState
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
-import com.android.quickstep.views.RecentsView
-import com.android.quickstep.views.RecentsViewContainer
import com.android.systemui.shared.recents.model.Task
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
/**
* Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper
* should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM.
*/
class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) {
- private lateinit var job: Job
+ private lateinit var overlayInitializedScope: CoroutineScope
private var uiState: TaskOverlayUiState = Disabled
- // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
- // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
- // This is using a lazy for now because the dependencies cannot be obtained without DI.
- private val taskOverlayViewModel by lazy {
- val recentsView =
- RecentsViewContainer.containerFromContext<RecentsViewContainer>(
- overlay.taskView.context
- )
- .getOverviewPanel<RecentsView<*, *>>()
- TaskOverlayViewModel(task, recentsView.mRecentsViewData!!, recentsView.mTasksRepository!!)
+ private val viewModel: TaskOverlayViewModel by lazy {
+ TaskOverlayViewModel(
+ task = task,
+ recentsViewData = RecentsDependencies.get(),
+ getThumbnailPositionUseCase = RecentsDependencies.get(),
+ recentTasksRepository = RecentsDependencies.get()
+ )
}
// TODO(b/331753115): TaskOverlay should listen for state changes and react.
val enabledState: Enabled
get() = uiState as Enabled
+ fun getThumbnailMatrix() = getThumbnailPositionState().matrix
+
+ private fun getThumbnailPositionState() =
+ viewModel.getThumbnailPositionState(
+ overlay.snapshotView.width,
+ overlay.snapshotView.height,
+ overlay.snapshotView.isLayoutRtl
+ )
+
fun init() {
- // TODO(b/335396935): This should be changed to TaskView's scope.
- job =
- MainScope().launch {
- taskOverlayViewModel.overlayState.collect {
- uiState = it
- if (it is Enabled) {
- Log.d(
- TAG,
- "initOverlay - taskId: ${task.key.id}, thumbnail: ${it.thumbnail}"
- )
- overlay.initOverlay(
- task,
- it.thumbnail,
- it.thumbnailMatrix,
- /* rotated= */ false
- )
- } else {
- Log.d(TAG, "reset - taskId: ${task.key.id}")
- overlay.reset()
- }
+ overlayInitializedScope =
+ CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskOverlayHelper"))
+ viewModel.overlayState
+ .onEach {
+ uiState = it
+ if (it is Enabled) {
+ initOverlay(it)
+ } else {
+ reset()
}
}
+ .launchIn(overlayInitializedScope)
+ overlay.snapshotView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ (uiState as? Enabled)?.let { initOverlay(it) }
+ }
+ }
+
+ private fun initOverlay(enabledState: Enabled) {
+ Log.d(TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${enabledState.thumbnail}")
+ with(getThumbnailPositionState()) {
+ overlay.initOverlay(task, enabledState.thumbnail, matrix, isRotated)
+ }
+ }
+
+ private fun reset() {
+ Log.d(TAG, "reset - taskId: ${task.key.id}")
+ overlay.reset()
}
fun destroy() {
- job.cancel()
+ overlayInitializedScope.cancel()
uiState = Disabled
- overlay.reset()
+ reset()
}
companion object {
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 47f32fb..4e13d1c 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -18,6 +18,9 @@
import android.graphics.Matrix
import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
@@ -25,31 +28,52 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
/** View model for TaskOverlay */
class TaskOverlayViewModel(
- task: Task,
+ private val task: Task,
recentsViewData: RecentsViewData,
- tasksRepository: RecentTasksRepository,
+ private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
+ recentTasksRepository: RecentTasksRepository,
) {
val overlayState =
combine(
recentsViewData.overlayEnabled,
recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
- tasksRepository.getThumbnailById(task.key.id)
+ recentTasksRepository.getThumbnailById(task.key.id)
) { isOverlayEnabled, isFullyVisible, thumbnailData ->
if (isOverlayEnabled && isFullyVisible) {
Enabled(
isRealSnapshot = (thumbnailData?.isRealSnapshot ?: false) && !task.isLocked,
thumbnailData?.thumbnail,
- // TODO(b/343101424): Use PreviewPositionHelper, listen from a common source
- // with
- // TaskThumbnailView.
- Matrix.IDENTITY_MATRIX
)
} else {
Disabled
}
}
.distinctUntilChanged()
+
+ fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
+ return runBlocking {
+ val matrix: Matrix
+ val isRotated: Boolean
+ when (
+ val thumbnailPositionState =
+ getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
+ ) {
+ is MatrixScaling -> {
+ matrix = thumbnailPositionState.matrix
+ isRotated = thumbnailPositionState.isRotated
+ }
+ is MissingThumbnail -> {
+ matrix = Matrix.IDENTITY_MATRIX
+ isRotated = false
+ }
+ }
+ ThumbnailPositionState(matrix, isRotated)
+ }
+ }
+
+ data class ThumbnailPositionState(val matrix: Matrix, val isRotated: Boolean)
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
similarity index 75%
rename from quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
rename to quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index d8729a6..6465645 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -14,19 +14,21 @@
* limitations under the License.
*/
-package com.android.quickstep.task.thumbnail
+package com.android.quickstep.task.viewmodel
import android.annotation.ColorInt
-import android.graphics.Rect
+import android.graphics.Matrix
import androidx.core.graphics.ColorUtils
import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState
import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.TaskThumbnail
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.systemui.shared.recents.model.Task
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -37,6 +39,7 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
@OptIn(ExperimentalCoroutinesApi::class)
class TaskThumbnailViewModel(
@@ -44,9 +47,10 @@
taskViewData: TaskViewData,
taskContainerData: TaskContainerData,
private val tasksRepository: RecentTasksRepository,
+ private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase
) {
private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
- private var boundTaskIsRunning = false
+ private lateinit var taskThumbnail: TaskThumbnail
/**
* Progress for changes in corner radius. progress: 0 = overview corner radius; 1 = fullscreen
@@ -66,16 +70,12 @@
taskFlow.map { taskVal ->
when {
taskVal == null -> Uninitialized
- boundTaskIsRunning -> LiveTile
+ taskThumbnail.isRunning -> LiveTile
isBackgroundOnly(taskVal) ->
BackgroundOnly(taskVal.colorBackground.removeAlpha())
isSnapshotState(taskVal) -> {
val bitmap = taskVal.thumbnail?.thumbnail!!
- Snapshot(
- bitmap,
- Rect(0, 0, bitmap.width, bitmap.height),
- taskVal.colorBackground.removeAlpha()
- )
+ Snapshot(bitmap, taskVal.colorBackground.removeAlpha())
}
else -> Uninitialized
}
@@ -84,10 +84,22 @@
.distinctUntilChanged()
fun bind(taskThumbnail: TaskThumbnail) {
- boundTaskIsRunning = taskThumbnail.isRunning
+ this.taskThumbnail = taskThumbnail
task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId)
}
+ fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
+ return runBlocking {
+ when (
+ val thumbnailPositionState =
+ getThumbnailPositionUseCase.run(taskThumbnail.taskId, width, height, isRtl)
+ ) {
+ is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
+ is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
+ }
+ }
+ }
+
private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
private fun isSnapshotState(task: Task): Boolean {
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
new file mode 100644
index 0000000..ec75d59
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.viewmodel
+
+import androidx.lifecycle.ViewModel
+
+class TaskViewModel(private val taskViewData: TaskViewData) : ViewModel() {
+ fun updateScale(scale: Float) {
+ taskViewData.scale.value = scale
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index cb44a1a..fcf9ab1 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -108,19 +108,28 @@
return false;
}
+ final SimpleOrientationTouchTransformer touchTransformer =
+ SimpleOrientationTouchTransformer.INSTANCE.get(mContext);
+ final int viewRotation = mRotationSupplier.get();
+ final boolean needTransform = viewRotation != ev.getSurfaceRotation();
if (action == ACTION_DOWN) {
mTouchInProgress = true;
+ if (needTransform) {
+ touchTransformer.updateTouchingOrientation(viewRotation);
+ }
initInputConsumerIfNeeded(/* isFromTouchDown= */ true);
} else if (action == ACTION_CANCEL || action == ACTION_UP) {
// Finish any pending actions
mTouchInProgress = false;
+ touchTransformer.clearTouchingOrientation();
if (mDestroyPending) {
destroy();
}
}
if (mInputConsumer != null) {
- SimpleOrientationTouchTransformer.INSTANCE.get(mContext).transform(ev,
- mRotationSupplier.get());
+ if (needTransform) {
+ touchTransformer.transform(ev, viewRotation);
+ }
mInputConsumer.onMotionEvent(ev);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3273809..a3d6359 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -191,8 +191,12 @@
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.recents.data.TasksRepository;
+import com.android.quickstep.recents.data.RecentTasksRepository;
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository;
+import com.android.quickstep.recents.data.RecentsRotationStateRepository;
+import com.android.quickstep.recents.di.RecentsDependencies;
import com.android.quickstep.recents.viewmodel.RecentsViewData;
+import com.android.quickstep.recents.viewmodel.RecentsViewModel;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AnimUtils;
@@ -239,8 +243,9 @@
/**
* A list of recent tasks.
+ *
* @param <CONTAINER_TYPE> : the container that should host recents view
- * @param <STATE_TYPE> : the type of base state that will be used
+ * @param <STATE_TYPE> : the type of base state that will be used
*/
public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewContainer,
@@ -389,7 +394,7 @@
view.setScaleX(scale);
view.setScaleY(scale);
if (enableRefactorTaskThumbnail()) {
- view.mRecentsViewData.getScale().setValue(scale);
+ view.mRecentsViewModel.updateScale(scale);
}
view.mLastComputedTaskStartPushOutDistance = null;
view.mLastComputedTaskEndPushOutDistance = null;
@@ -461,11 +466,6 @@
private static final float FOREGROUND_SCRIM_TINT = 0.32f;
- @Nullable
- public final RecentsViewData mRecentsViewData = new RecentsViewData();
- @Nullable
- public final TasksRepository mTasksRepository;
-
protected final RecentsOrientedState mOrientationState;
protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
@Nullable
@@ -720,10 +720,12 @@
private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() {
@Override
- public void onSplitSelectionConfirmed() { }
+ public void onSplitSelectionConfirmed() {
+ }
@Override
- public void onSplitSelectionActive() { }
+ public void onSplitSelectionActive() {
+ }
@Override
public void onSplitSelectionExit(boolean launchedSplit) {
@@ -802,10 +804,13 @@
*/
private boolean mAnyTaskHasBeenDismissed;
+ private final RecentsViewModel mRecentsViewModel;
+
public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
BaseContainerInterface sizeStrategy) {
super(context, attrs, defStyleAttr);
setEnableFreeScroll(true);
+
mSizeStrategy = sizeStrategy;
mContainer = RecentsViewContainer.containerFromContext(context);
mOrientationState = new RecentsOrientedState(
@@ -813,18 +818,30 @@
final int rotation = mContainer.getDisplay().getRotation();
mOrientationState.setRecentsRotation(rotation);
+ // Start Recents Dependency graph
+ if (enableRefactorTaskThumbnail()) {
+ RecentsDependencies recentsDependencies = RecentsDependencies.Companion.initialize(
+ this);
+ mRecentsViewModel = new RecentsViewModel(
+ recentsDependencies.inject(RecentTasksRepository.class),
+ recentsDependencies.inject(RecentsViewData.class)
+ );
+
+ recentsDependencies.provide(RecentsRotationStateRepository.class,
+ () -> new RecentsRotationStateRepository(mOrientationState));
+
+ recentsDependencies.provide(RecentsDeviceProfileRepository.class,
+ () -> new RecentsDeviceProfileRepository(mContainer));
+ } else {
+ mRecentsViewModel = null;
+ }
+
mScrollHapticMinGapMillis = getResources()
.getInteger(R.integer.recentsScrollHapticMinGapMillis);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mModel = RecentsModel.INSTANCE.get(context);
mIdp = InvariantDeviceProfile.INSTANCE.get(context);
- if (enableRefactorTaskThumbnail()) {
- mTasksRepository = new TasksRepository(
- mModel, mModel.getThumbnailCache(), mModel.getIconCache());
- } else {
- mTasksRepository = null;
- }
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
@@ -1067,6 +1084,7 @@
/**
* Update the thumbnail(s) of the relevant TaskView.
+ *
* @param refreshNow Refresh immediately if it's true.
*/
@Nullable
@@ -1127,6 +1145,7 @@
/**
* See overridden implementations
+ *
* @return {@code true} if child TaskViews can be launched when user taps on them
*/
protected boolean canLaunchFullscreenTask() {
@@ -2051,7 +2070,7 @@
public void setFullscreenProgress(float fullscreenProgress) {
mFullscreenProgress = fullscreenProgress;
if (enableRefactorTaskThumbnail()) {
- mRecentsViewData.getFullscreenProgress().setValue(mFullscreenProgress);
+ mRecentsViewModel.updateFullscreenProgress(mFullscreenProgress);
}
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
@@ -2449,7 +2468,7 @@
}
}
if (enableRefactorTaskThumbnail()) {
- mTasksRepository.setVisibleTasks(visibleTaskIds);
+ mRecentsViewModel.updateVisibleTasks(visibleTaskIds);
}
}
@@ -2516,6 +2535,13 @@
mFocusedTaskViewId = -1;
mAnyTaskHasBeenDismissed = false;
+
+ if (enableRefactorTaskThumbnail()) {
+ // TODO(b/353917593): RecentsView is never destroyed, so its dependencies need to
+ // be cleaned up during the reset, but re-created when RecentsView is "resumed".
+ // RecentsDependencies.Companion.destroy();
+ }
+
Log.d(TAG, "reset - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile
+ ", mRecentsAnimationController: " + mRecentsAnimationController);
if (mEnableDrawingLiveTile) {
@@ -2626,6 +2652,7 @@
/**
* Get the index of the task view whose id matches {@param taskId}.
+ *
* @return -1 if there is no task view for the task id, else the index of the task view.
*/
public int getTaskIndexForId(int taskId) {
@@ -2641,7 +2668,7 @@
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState
.getFilter(mFilterState.getPackageNameToFilter()));
if (enableRefactorTaskThumbnail()) {
- mTasksRepository.getAllTaskData(/* forceRefresh = */ true);
+ mRecentsViewModel.refreshAllTaskData();
}
}
}
@@ -2810,7 +2837,7 @@
int[] runningTaskIds = Arrays.stream(runningTasks).mapToInt(task -> task.key.id).toArray();
TaskView matchingTaskView = null;
if (hasDesktopTask(runningTasks) && runningTaskIds.length == 1) {
- // TODO(b/249371338): Unsure if it's expected, desktop runningTasks only have a single
+ // TODO(b/342635213): Unsure if it's expected, desktop runningTasks only have a single
// taskId, therefore we match any DesktopTaskView that contains the runningTaskId.
TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]);
if (taskview instanceof DesktopTaskView) {
@@ -2851,7 +2878,7 @@
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
// load later anyways
- ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
+ ((GroupedTaskView) taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
mOrientationState, mTaskOverlayFactory, mSplitBoundsConfig);
} else {
taskView = getTaskViewFromPool(TaskViewType.SINGLE);
@@ -3179,7 +3206,7 @@
(mIsRtl
? mLastComputedTaskSize.right
: deviceProfile.widthPx - mLastComputedTaskSize.left)
- - longRowWidth - deviceProfile.overviewGridSideMargin;
+ - longRowWidth - deviceProfile.overviewGridSideMargin;
clearAllShortTotalWidthTranslation = mIsRtl
? -mClearAllShortTotalWidthTranslation : mClearAllShortTotalWidthTranslation;
if (snappedTaskRowWidth == longRowWidth) {
@@ -3209,9 +3236,9 @@
(mIsRtl
? mLastComputedTaskSize.left
: deviceProfile.widthPx - mLastComputedTaskSize.right)
- - deviceProfile.overviewGridSideMargin - mPageSpacing
- + (mTaskWidth - snappedTaskView.getLayoutParams().width)
- - mClearAllShortTotalWidthTranslation;
+ - deviceProfile.overviewGridSideMargin - mPageSpacing
+ + (mTaskWidth - snappedTaskView.getLayoutParams().width)
+ - mClearAllShortTotalWidthTranslation;
if (distanceFromClearAll < minimumDistance) {
int distanceDifference = minimumDistance - distanceFromClearAll;
snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference;
@@ -3291,12 +3318,12 @@
mLayoutTransition.addTransitionListener(new TransitionListener() {
@Override
public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
- View view, int i) {
+ View view, int i) {
}
@Override
public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
- View view, int i) {
+ View view, int i) {
// When the unpinned task is added, snap to first page and disable transitions
if (view instanceof TaskView) {
snapToPage(0);
@@ -3448,11 +3475,13 @@
/**
* Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
- * @param dismissedTaskView the {@link TaskView} to be dismissed
- * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated
- * @param shouldRemoveTask whether the associated {@link Task} should be removed from
- * ActivityManager after dismissal
- * @param duration duration of the animation
+ *
+ * @param dismissedTaskView the {@link TaskView} to be dismissed
+ * @param animateTaskView whether the {@link TaskView} to be dismissed should be
+ * animated
+ * @param shouldRemoveTask whether the associated {@link Task} should be removed from
+ * ActivityManager after dismissal
+ * @param duration duration of the animation
* @param dismissingForSplitSelection task dismiss animation is used for entering split
* selection state from app icon
*/
@@ -3717,7 +3746,7 @@
float animationEndProgress = isSplitSelectionActive()
? Utilities.boundToRange(splitTimings.getGridSlideStartOffset()
- + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
+ + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
: 1f;
// Slide tiles in horizontally to fill dismissed area
@@ -3761,16 +3790,16 @@
// dismissed index or next focused index. Offset successive task dismissal
// durations for a staggered effect.
distanceFromDismissedTask++;
- int staggerColumn = isStagingFocusedTask
+ int staggerColumn = isStagingFocusedTask
? (int) Math.ceil(distanceFromDismissedTask / 2f)
: distanceFromDismissedTask;
// Set timings based on if user is initiating splitscreen on the focused task,
// or splitting/dismissing some other task.
float animationStartProgress = isStagingFocusedTask
? Utilities.boundToRange(
- splitTimings.getGridSlideStartOffset()
- + (splitTimings.getGridSlideStaggerOffset()
- * staggerColumn),
+ splitTimings.getGridSlideStartOffset()
+ + (splitTimings.getGridSlideStaggerOffset()
+ * staggerColumn),
0f,
dismissTranslationInterpolationEnd)
: Utilities.boundToRange(
@@ -3779,9 +3808,9 @@
* staggerColumn, 0f, dismissTranslationInterpolationEnd);
float animationEndProgress = isStagingFocusedTask
? Utilities.boundToRange(
- splitTimings.getGridSlideStartOffset()
- + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
- + splitTimings.getGridSlideDurationOffset(),
+ splitTimings.getGridSlideStartOffset()
+ + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
+ + splitTimings.getGridSlideDurationOffset(),
0f,
dismissTranslationInterpolationEnd)
: dismissTranslationInterpolationEnd;
@@ -3997,8 +4026,9 @@
RecentsView.this);
int taskSize = (int) (
getPagedOrientationHandler().getMeasuredSize(
- taskView) * taskView
- .getSizeAdjustment(/*fullscreenEnabled=*/false));
+ taskView) * taskView
+ .getSizeAdjustment(/*fullscreenEnabled=*/
+ false));
int taskEnd = taskStart + taskSize;
shouldRebalance = taskEnd >= screenEnd - mPageSpacing;
@@ -4641,6 +4671,7 @@
/**
* Computes the distance to offset the given child such that it is completely offscreen when
* translating away from the given midpoint.
+ *
* @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
*/
private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
@@ -4862,7 +4893,7 @@
mContainer.getDeviceProfile(),
mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
primaryTaskSelected);
- builder.addOnFrameCallback(() ->{
+ builder.addOnFrameCallback(() -> {
// TODO(b/334826842): Handle splash icon for new TTV.
if (!enableRefactorTaskThumbnail()) {
taskContainer.getThumbnailViewDeprecated().refreshSplashView();
@@ -4886,17 +4917,21 @@
* @param containerTaskView If our second selected app is currently running in Recents, this is
* the "container" TaskView from Recents. If we are starting a fresh
* instance of the app from an Intent, this will be null.
- * @param task The Task corresponding to our second selected app. If we are starting a fresh
- * instance of the app from an Intent, this will be null.
- * @param drawable The Drawable corresponding to our second selected app's icon.
- * @param secondView The View representing the current space on the screen where the second app
- * is (either the ThumbnailView or the tapped icon).
- * @param intent If we are launching a fresh instance of the app, this is the Intent for it. If
- * the second app is already running in Recents, this will be null.
- * @param user If we are launching a fresh instance of the app, this is the UserHandle for it.
- * If the second app is already running in Recents, this will be null.
+ * @param task The Task corresponding to our second selected app. If we are
+ * starting a fresh
+ * instance of the app from an Intent, this will be null.
+ * @param drawable The Drawable corresponding to our second selected app's icon.
+ * @param secondView The View representing the current space on the screen where the
+ * second app
+ * is (either the ThumbnailView or the tapped icon).
+ * @param intent If we are launching a fresh instance of the app, this is the Intent
+ * for it. If
+ * the second app is already running in Recents, this will be null.
+ * @param user If we are launching a fresh instance of the app, this is the
+ * UserHandle for it.
+ * If the second app is already running in Recents, this will be null.
* @return true if waiting for confirmation of second app or if split animations are running,
- * false otherwise
+ * false otherwise
*/
public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user,
@@ -4984,7 +5019,7 @@
"Second tile selected");
// Fade out all other views underneath placeholders
- ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
+ ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA, 1, 0);
pendingAnimation.add(tvFade, DECELERATE_2, SpringProperty.DEFAULT);
pendingAnimation.buildAnim().start();
return true;
@@ -5132,7 +5167,7 @@
if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
- mEmptyMessagePaint, availableWidth)
+ mEmptyMessagePaint, availableWidth)
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.build();
int totalHeight = mEmptyTextLayout.getHeight()
@@ -5259,7 +5294,7 @@
updateScrollSynchronously();
int targetSysUiFlags = tv.getTaskContainers().getFirst().getSysUiStatusNavFlags();
- final boolean[] passedOverviewThreshold = new boolean[] {false};
+ final boolean[] passedOverviewThreshold = new boolean[]{false};
ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
progressAnim.addUpdateListener(animator -> {
// Once we pass a certain threshold, update the sysui flags to match the target
@@ -5380,7 +5415,7 @@
fullyVisibleTaskIds.addAll(taskIds);
}
}
- mRecentsViewData.getSettledFullyVisibleTaskIds().setValue(fullyVisibleTaskIds);
+ mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds);
}
}
@@ -5779,6 +5814,7 @@
* Sets whether or not we should clamp the scroll offset.
* This is used to avoid x-axis movement when swiping up transient taskbar.
* Should only be set at the beginning and end of the gesture, otherwise a jump may occur.
+ *
* @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is
* met.
*/
@@ -5811,11 +5847,11 @@
// Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that
// the page can move freely given there's no visual indication why it shouldn't.
int overScrollShift = mAdjacentPageHorizontalOffset > 0
- ? (int) Utilities.mapRange(
- mAdjacentPageHorizontalOffset,
- getOverScrollShift(),
- getUndampedOverScrollShift())
- : getOverScrollShift();
+ ? (int) Utilities.mapRange(
+ mAdjacentPageHorizontalOffset,
+ getOverScrollShift(),
+ getUndampedOverScrollShift())
+ : getOverScrollShift();
return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this)
+ overScrollShift + getOffsetFromScrollPosition(pageIndex);
}
@@ -5932,7 +5968,10 @@
if (mOverlayEnabled != overlayEnabled) {
mOverlayEnabled = overlayEnabled;
updateEnabledOverlays();
- mRecentsViewData.getOverlayEnabled().setValue(overlayEnabled);
+
+ if (enableRefactorTaskThumbnail()) {
+ mRecentsViewModel.setOverlayEnabled(overlayEnabled);
+ }
}
}
@@ -6120,7 +6159,7 @@
public boolean showAsGrid() {
return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
&& mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget)
- .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
+ .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
}
private boolean showAsFullscreen() {
@@ -6161,7 +6200,7 @@
/**
* @return Corner radius in pixel value for PiP window, which is updated via
- * {@link #mIPipAnimationListener}
+ * {@link #mIPipAnimationListener}
*/
public int getPipCornerRadius() {
return mPipCornerRadius;
@@ -6169,7 +6208,7 @@
/**
* @return Shadow radius in pixel value for PiP window, which is updated via
- * {@link #mIPipAnimationListener}
+ * {@link #mIPipAnimationListener}
*/
public int getPipShadowRadius() {
return mPipShadowRadius;
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 74d120f..79725c6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -29,10 +29,15 @@
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskUtils
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
+import com.android.quickstep.recents.di.getScope
+import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.usecase.GetThumbnailUseCase
import com.android.quickstep.task.thumbnail.TaskThumbnail
import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.util.GetThumbnailUseCase
import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.systemui.shared.recents.model.Task
/** Holder for all Task dependent information. */
@@ -55,21 +60,22 @@
taskOverlayFactory: TaskOverlayFactory
) {
val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
- val taskContainerData = TaskContainerData()
-
- private val getThumbnailUseCase by lazy {
- // TODO(b/335649589): Ideally create and obtain this from DI.
- val recentsView =
- RecentsViewContainer.containerFromContext<RecentsViewContainer>(
- overlay.taskView.context
- )
- .getOverviewPanel<RecentsView<*, *>>()
- GetThumbnailUseCase(recentsView.mTasksRepository!!)
- }
+ lateinit var taskContainerData: TaskContainerData
+ private val getThumbnailUseCase: GetThumbnailUseCase by RecentsDependencies.inject()
+ private val taskThumbnailViewModel: TaskThumbnailViewModel by
+ RecentsDependencies.inject(snapshotView)
init {
if (enableRefactorTaskThumbnail()) {
require(snapshotView is TaskThumbnailView)
+ taskContainerData = RecentsDependencies.get(this)
+ RecentsDependencies.getScope(snapshotView).apply {
+ val taskViewScope = RecentsDependencies.getScope(taskView)
+ linkTo(taskViewScope)
+
+ val taskContainerScope = RecentsDependencies.getScope(this)
+ linkTo(taskContainerScope)
+ }
} else {
require(snapshotView is TaskThumbnailViewDeprecated)
}
@@ -146,12 +152,10 @@
overlay.destroy()
}
- // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
- // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
fun bindThumbnailView() {
// TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but
// this should be decided inside TaskThumbnailViewModel.
- thumbnailView.viewModel.bind(TaskThumbnail(task.key.id, taskView.isRunningTask))
+ taskThumbnailViewModel.bind(TaskThumbnail(task.key.id, taskView.isRunningTask))
}
fun setOverlayEnabled(enabled: Boolean) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 004003c..f2f036a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -80,8 +80,10 @@
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.task.viewmodel.TaskViewModel
import com.android.quickstep.util.ActiveGestureErrorDetector
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.BorderAnimator
@@ -115,7 +117,8 @@
@IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS)
annotation class TaskDataChanges
- val taskViewData = TaskViewData(type)
+ private lateinit var taskViewModel: TaskViewModel
+
val taskIds: IntArray
/** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
get() = taskContainers.map { it.task.key.id }.toIntArray()
@@ -441,6 +444,11 @@
init {
setOnClickListener { _ -> onClick() }
+
+ if (enableRefactorTaskThumbnail()) {
+ taskViewModel = RecentsDependencies.get(this, "TaskViewType" to type)
+ }
+
val keyboardFocusHighlightEnabled =
(ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline())
val cursorHoverStatesEnabled = enableCursorHoverStates()
@@ -638,6 +646,7 @@
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory
) {
+
cancelPendingLoadTasks()
taskContainers =
listOf(
@@ -1404,7 +1413,7 @@
scaleX = scale
scaleY = scale
if (enableRefactorTaskThumbnail()) {
- taskViewData.scale.value = scale
+ taskViewModel.updateScale(scale)
}
updateSnapshotRadius()
}
@@ -1483,8 +1492,6 @@
/** Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). */
fun notifyIsRunningTaskUpdated() {
- // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
- // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
taskContainers.forEach { it.bindThumbnailView() }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
new file mode 100644
index 0000000..8bad3b9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Path
+import android.graphics.drawable.ColorDrawable
+import android.view.LayoutInflater
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.R
+import com.android.wm.shell.common.bubbles.BubbleInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleViewTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var bubbleView: BubbleView
+ private lateinit var overflowView: BubbleView
+ private lateinit var bubble: BubbleBarBubble
+
+ @Test
+ fun hasUnseenContent_bubble() {
+ setupBubbleViews()
+ assertThat(bubbleView.hasUnseenContent()).isTrue()
+
+ bubbleView.markSeen()
+ assertThat(bubbleView.hasUnseenContent()).isFalse()
+ }
+
+ @Test
+ fun hasUnseenContent_overflow() {
+ setupBubbleViews()
+ assertThat(overflowView.hasUnseenContent()).isFalse()
+ }
+
+ private fun setupBubbleViews() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ val inflater = LayoutInflater.from(context)
+
+ val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
+ overflowView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView
+ overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
+
+ val bubbleInfo =
+ BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false)
+ bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView
+ bubble =
+ BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
+ bubbleView.setBubble(bubble)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt
index 60c8dec..d417790 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt
@@ -16,12 +16,13 @@
package com.android.launcher3.taskbar.rules
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.launcher3.ConstantItem
-import com.android.launcher3.LauncherPrefs
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.launcher3.Flags.FLAG_ENABLE_TASKBAR_PINNING
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
-import kotlin.reflect.KProperty
+import com.android.launcher3.util.DisplayController
+import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@@ -39,33 +40,26 @@
*/
class TaskbarPinningPreferenceRule(context: TaskbarWindowSandboxContext) : TestRule {
- private val prefs = LauncherPrefs.get(context)
+ private val setFlagsRule =
+ SetFlagsRule(FlagsParameterization(mapOf(FLAG_ENABLE_TASKBAR_PINNING to true)))
+ private val pinningRule = TaskbarPreferenceRule(context, TASKBAR_PINNING)
+ private val desktopPinningRule = TaskbarPreferenceRule(context, TASKBAR_PINNING_IN_DESKTOP_MODE)
+ private val ruleChain =
+ RuleChain.outerRule(setFlagsRule).around(pinningRule).around(desktopPinningRule)
- var isPinned by PinningPreference(TASKBAR_PINNING)
- var isPinnedInDesktopMode by PinningPreference(TASKBAR_PINNING_IN_DESKTOP_MODE)
+ var isPinned by pinningRule::value
+ var isPinnedInDesktopMode by desktopPinningRule::value
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
- val wasPinned = isPinned
- val wasPinnedInDesktopMode = isPinnedInDesktopMode
+ DisplayController.enableTaskbarModePreferenceForTests(true)
try {
- base.evaluate()
+ ruleChain.apply(base, description).evaluate()
} finally {
- isPinned = wasPinned
- isPinnedInDesktopMode = wasPinnedInDesktopMode
+ DisplayController.enableTaskbarModePreferenceForTests(false)
}
}
}
}
-
- private inner class PinningPreference(private val constantItem: ConstantItem<Boolean>) {
- operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
- return prefs.get(constantItem)
- }
-
- operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
- getInstrumentation().runOnMainSync { prefs.put(constantItem, value) }
- }
- }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
index 3f0a238..a709133 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
@@ -19,6 +19,8 @@
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.window.WindowManagerProxy
+import com.google.android.apps.nexuslauncher.deviceemulator.TestWindowManagerProxy
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.Description
@@ -51,6 +53,11 @@
@Test
fun testEnableDesktopPinning_verifyDisplayController() {
+ context.applicationContext.putObject(
+ WindowManagerProxy.INSTANCE,
+ TestWindowManagerProxy(context).apply { isInDesktopMode = true },
+ )
+
onSetup {
preferenceRule.isPinned = false
preferenceRule.isPinnedInDesktopMode = true
@@ -60,6 +67,11 @@
@Test
fun testDisableDesktopPinning_verifyDisplayController() {
+ context.applicationContext.putObject(
+ WindowManagerProxy.INSTANCE,
+ TestWindowManagerProxy(context).apply { isInDesktopMode = true },
+ )
+
onSetup {
preferenceRule.isPinned = false
preferenceRule.isPinnedInDesktopMode = false
@@ -83,12 +95,14 @@
/** Executes [runTest] after the [preferenceRule] setup phase completes. */
private fun onSetup(runTest: () -> Unit) {
- preferenceRule.apply(
- object : Statement() {
- override fun evaluate() = runTest()
- },
- DESCRIPTION,
- )
+ preferenceRule
+ .apply(
+ object : Statement() {
+ override fun evaluate() = runTest()
+ },
+ DESCRIPTION,
+ )
+ .evaluate()
}
private companion object {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
new file mode 100644
index 0000000..a76a77d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.ConstantItem
+import com.android.launcher3.LauncherPrefs
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule for modifying a Taskbar preference.
+ *
+ * The original preference value is restored on teardown.
+ */
+class TaskbarPreferenceRule<T : Any>(
+ context: TaskbarWindowSandboxContext,
+ private val constantItem: ConstantItem<T>
+) : TestRule {
+
+ private val prefs = LauncherPrefs.get(context)
+
+ var value: T
+ get() = prefs.get(constantItem)
+ set(value) = getInstrumentation().runOnMainSync { prefs.put(constantItem, value) }
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ val originalValue = value
+ try {
+ base.evaluate()
+ } finally {
+ value = originalValue
+ }
+ }
+ }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
new file mode 100644
index 0000000..22d2079
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarPreferenceRuleTest {
+
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+ private val preferenceRule = TaskbarPreferenceRule(context, TASKBAR_PINNING)
+
+ @Test
+ fun testSetup_toggleBoolean_updatesPreferences() {
+ val originalValue = preferenceRule.value
+ onSetup {
+ preferenceRule.value = !preferenceRule.value
+ assertThat(preferenceRule.value).isNotEqualTo(originalValue)
+ }
+ }
+
+ @Test
+ fun testTeardown_afterTogglingBoolean_preferenceReset() {
+ val originalValue = preferenceRule.value
+ onSetup { preferenceRule.value = !preferenceRule.value }
+ assertThat(preferenceRule.value).isEqualTo(originalValue)
+ }
+
+ private fun onSetup(runTest: () -> Unit) {
+ preferenceRule
+ .apply(
+ object : Statement() {
+ override fun evaluate() = runTest()
+ },
+ DESCRIPTION,
+ )
+ .evaluate()
+ }
+
+ private companion object {
+ private val DESCRIPTION =
+ Description.createSuiteDescription(TaskbarPreferenceRule::class.java)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index a966d2a..bbcf566 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -29,10 +29,10 @@
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
+import com.android.launcher3.taskbar.TaskbarViewController
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
-import com.android.launcher3.util.ModelTestExtensions.loadModelSync
import com.android.launcher3.util.TestUtil
import com.android.quickstep.AllAppsActionManager
import com.android.quickstep.TouchInteractionService
@@ -152,7 +152,7 @@
}
try {
- LauncherAppState.getInstance(context).model.loadModelSync()
+ TaskbarViewController.enableModelLoadingForTests(false)
// Replace Launcher Taskbar window with test instance.
instrumentation.runOnMainSync {
@@ -167,6 +167,8 @@
taskbarManager.destroy()
launcherTaskbarManager?.setSuspended(false)
}
+
+ TaskbarViewController.enableModelLoadingForTests(true)
}
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index 321e7a9..ee21df8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -50,7 +50,7 @@
)
}
- override fun getApplicationContext() = application
+ override fun getApplicationContext(): SandboxContext = application
companion object {
/** Creates a [TaskbarWindowSandboxContext] to sandbox [base] for Taskbar tests. */
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt
new file mode 100644
index 0000000..24f9696
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt
@@ -0,0 +1,139 @@
+package com.android.quickstep
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED)
+class LauncherRestoreEventLoggerImplTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val mLauncherModelHelper = LauncherModelHelper()
+ private val mSandboxContext: SandboxModelContext = mLauncherModelHelper.sandboxContext
+ private lateinit var loggerUnderTest: LauncherRestoreEventLoggerImpl
+
+ @Before
+ fun setup() {
+ loggerUnderTest = LauncherRestoreEventLoggerImpl(mSandboxContext)
+ }
+
+ @After
+ fun teardown() {
+ loggerUnderTest.restoreEventLogger.clearData()
+ mLauncherModelHelper.destroy()
+ }
+
+ @Test
+ fun `logLauncherItemsRestoreFailed logs multiple items as failing restore`() {
+ // Given
+ val expectedDataType = "application"
+ val expectedError = "test_failure"
+ // When
+ loggerUnderTest.logLauncherItemsRestoreFailed(
+ dataType = expectedDataType,
+ count = 5,
+ error = expectedError
+ )
+ // Then
+ val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+ assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+ assertThat(actualResult.successCount).isEqualTo(0)
+ assertThat(actualResult.failCount).isEqualTo(5)
+ assertThat(actualResult.errors.keys).containsExactly(expectedError)
+ }
+
+ @Test
+ fun `logLauncherItemsRestored logs multiple items as restored`() {
+ // Given
+ val expectedDataType = "application"
+ // When
+ loggerUnderTest.logLauncherItemsRestored(dataType = expectedDataType, count = 5)
+ // Then
+ val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+ assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+ assertThat(actualResult.successCount).isEqualTo(5)
+ assertThat(actualResult.failCount).isEqualTo(0)
+ assertThat(actualResult.errors.keys).isEmpty()
+ }
+
+ @Test
+ fun `logSingleFavoritesItemRestored logs a single Favorites Item as restored`() {
+ // Given
+ val expectedDataType = "widget"
+ // When
+ loggerUnderTest.logSingleFavoritesItemRestored(favoritesId = Favorites.ITEM_TYPE_APPWIDGET)
+ // Then
+ val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+ assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+ assertThat(actualResult.successCount).isEqualTo(1)
+ assertThat(actualResult.failCount).isEqualTo(0)
+ assertThat(actualResult.errors.keys).isEmpty()
+ }
+
+ @Test
+ fun `logSingleFavoritesItemRestoreFailed logs a single Favorites Item as failing restore`() {
+ // Given
+ val expectedDataType = "widget"
+ val expectedError = "test_failure"
+ // When
+ loggerUnderTest.logSingleFavoritesItemRestoreFailed(
+ favoritesId = Favorites.ITEM_TYPE_APPWIDGET,
+ error = expectedError
+ )
+ // Then
+ val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+ assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+ assertThat(actualResult.successCount).isEqualTo(0)
+ assertThat(actualResult.failCount).isEqualTo(1)
+ assertThat(actualResult.errors.keys).containsExactly(expectedError)
+ }
+
+ @Test
+ fun `logFavoritesItemsRestoreFailed logs multiple Favorites Items as failing restore`() {
+ // Given
+ val expectedDataType = "deep_shortcut"
+ val expectedError = "test_failure"
+ // When
+ loggerUnderTest.logFavoritesItemsRestoreFailed(
+ favoritesId = Favorites.ITEM_TYPE_DEEP_SHORTCUT,
+ count = 5,
+ error = expectedError
+ )
+ // Then
+ val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+ assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+ assertThat(actualResult.successCount).isEqualTo(0)
+ assertThat(actualResult.failCount).isEqualTo(5)
+ assertThat(actualResult.errors.keys).containsExactly(expectedError)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
new file mode 100644
index 0000000..070eeaf
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.logging
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
+import com.android.launcher3.logging.InstanceId
+import com.android.launcher3.logging.StatsLogManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+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.mockito.kotlin.any
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class SettingsChangeLoggerTest {
+ private val mContext: Context = ApplicationProvider.getApplicationContext()
+
+ private val mInstanceId = InstanceId.fakeInstanceId(1)
+
+ private lateinit var mSystemUnderTest: SettingsChangeLogger
+
+ @Mock private lateinit var mStatsLogManager: StatsLogManager
+
+ @Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger
+
+ @Captor private lateinit var mEventCaptor: ArgumentCaptor<StatsLogManager.EventEnum>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(mStatsLogManager.logger()).doReturn(mMockLogger)
+ whenever(mStatsLogManager.logger().withInstanceId(any())).doReturn(mMockLogger)
+
+ mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+ }
+
+ @After
+ fun tearDown() {
+ mSystemUnderTest.close()
+ }
+
+ @Test
+ fun loggingPrefs_correctDefaultValue() {
+ assertThat(mSystemUnderTest.loggingPrefs["pref_allowRotation"]!!.defaultValue).isFalse()
+ assertThat(mSystemUnderTest.loggingPrefs["pref_add_icon_to_home"]!!.defaultValue).isTrue()
+ assertThat(mSystemUnderTest.loggingPrefs["pref_overview_action_suggestions"]!!.defaultValue)
+ .isTrue()
+ assertThat(mSystemUnderTest.loggingPrefs["pref_smartspace_home_screen"]!!.defaultValue)
+ .isTrue()
+ assertThat(mSystemUnderTest.loggingPrefs["pref_enable_minus_one"]!!.defaultValue).isTrue()
+ }
+
+ @Test
+ fun logSnapshot_defaultValue() {
+ mSystemUnderTest.logSnapshot(mInstanceId)
+
+ verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
+ val capturedEvents = mEventCaptor.allValues
+ assertThat(capturedEvents.isNotEmpty()).isTrue()
+ verifyDefaultEvent(capturedEvents)
+ // pref_allowRotation false
+ assertThat(capturedEvents.any { it.id == 616 }).isTrue()
+ }
+
+ @Test
+ fun logSnapshot_updateValue() {
+ LauncherPrefs.get(mContext)
+ .put(
+ item =
+ backedUpItem(
+ sharedPrefKey = "pref_allowRotation",
+ defaultValue = false,
+ ),
+ value = true
+ )
+
+ mSystemUnderTest.logSnapshot(mInstanceId)
+
+ verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
+ val capturedEvents = mEventCaptor.allValues
+ assertThat(capturedEvents.isNotEmpty()).isTrue()
+ verifyDefaultEvent(capturedEvents)
+ // pref_allowRotation true
+ assertThat(capturedEvents.any { it.id == 615 }).isTrue()
+ }
+
+ private fun verifyDefaultEvent(capturedEvents: MutableList<StatsLogManager.EventEnum>) {
+ // LAUNCHER_NOTIFICATION_DOT_ENABLED
+ assertThat(capturedEvents.any { it.id == 611 }).isTrue()
+ // LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON
+ assertThat(capturedEvents.any { it.id == 625 }).isTrue()
+ // LAUNCHER_THEMED_ICON_DISABLED
+ assertThat(capturedEvents.any { it.id == 837 }).isTrue()
+ // pref_add_icon_to_home true
+ assertThat(capturedEvents.any { it.id == 613 }).isTrue()
+ // pref_overview_action_suggestions true
+ assertThat(capturedEvents.any { it.id == 619 }).isTrue()
+ // pref_smartspace_home_screen true
+ assertThat(capturedEvents.any { it.id == 621 }).isTrue()
+ // pref_enable_minus_one true
+ assertThat(capturedEvents.any { it.id == 617 }).isTrue()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
new file mode 100644
index 0000000..eff926d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.FakeInvariantDeviceProfileTest
+import com.android.quickstep.views.RecentsViewContainer
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [RecentsDeviceProfileRepository] */
+@RunWith(AndroidJUnit4::class)
+class RecentsDeviceProfileRepositoryTest : FakeInvariantDeviceProfileTest() {
+ private val recentsViewContainer = mock<RecentsViewContainer>()
+
+ private val systemUnderTest = RecentsDeviceProfileRepository(recentsViewContainer)
+
+ @Test
+ fun deviceProfileMappedCorrectly() {
+ initializeVarsForTablet()
+ val tabletDeviceProfile = newDP()
+ whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
+
+ assertThat(systemUnderTest.getRecentsDeviceProfile())
+ .isEqualTo(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen = true))
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
new file mode 100644
index 0000000..1f4da26
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import android.view.Surface.ROTATION_90
+import com.android.quickstep.util.RecentsOrientedState
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [RecentsRotationStateRepository] */
+class RecentsRotationStateRepositoryTest {
+ private val recentsOrientedState = mock<RecentsOrientedState>()
+
+ private val systemUnderTest = RecentsRotationStateRepository(recentsOrientedState)
+
+ @Test
+ fun orientedStateMappedCorrectly() {
+ whenever(recentsOrientedState.recentsActivityRotation).thenReturn(ROTATION_90)
+
+ assertThat(systemUnderTest.getRecentsRotationState())
+ .isEqualTo(
+ RecentsRotationStateRepository.RecentsRotationState(activityRotation = ROTATION_90)
+ )
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
new file mode 100644
index 0000000..e657d59
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.usecase
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.view.Surface.ROTATION_90
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/** Test for [GetThumbnailPositionUseCase] */
+@RunWith(AndroidJUnit4::class)
+class GetThumbnailPositionUseCaseTest {
+ private val task =
+ Task(Task.TaskKey(TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ colorBackground = Color.BLACK
+ }
+ private val thumbnailData =
+ ThumbnailData(
+ thumbnail =
+ mock<Bitmap>().apply {
+ whenever(width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(height).thenReturn(THUMBNAIL_HEIGHT)
+ }
+ )
+
+ private val deviceProfileRepository = mock<RecentsDeviceProfileRepository>()
+ private val rotationStateRepository = mock<RecentsRotationStateRepository>()
+ private val tasksRepository = FakeTasksRepository()
+ private val previewPositionHelper = mock<PreviewPositionHelper>()
+
+ private val systemUnderTest =
+ GetThumbnailPositionUseCase(
+ deviceProfileRepository,
+ rotationStateRepository,
+ tasksRepository,
+ previewPositionHelper
+ )
+
+ @Test
+ fun invisibleTask_returnsIdentityMatrix() = runTest {
+ tasksRepository.seedTasks(listOf(task))
+
+ assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true))
+ .isInstanceOf(MissingThumbnail::class.java)
+ }
+
+ @Test
+ fun visibleTaskWithoutThumbnailData_returnsIdentityMatrix() = runTest {
+ tasksRepository.seedTasks(listOf(task))
+ tasksRepository.setVisibleTasks(listOf(TASK_ID))
+
+ assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true))
+ .isInstanceOf(MissingThumbnail::class.java)
+ }
+
+ @Test
+ fun visibleTaskWithThumbnailData_returnsTransformedMatrix() = runTest {
+ tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
+ tasksRepository.seedTasks(listOf(task))
+ tasksRepository.setVisibleTasks(listOf(TASK_ID))
+
+ val isLargeScreen = true
+ val activityRotation = ROTATION_90
+ val isRtl = true
+ val isRotated = true
+
+ whenever(deviceProfileRepository.getRecentsDeviceProfile())
+ .thenReturn(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen))
+ whenever(rotationStateRepository.getRecentsRotationState())
+ .thenReturn(RecentsRotationStateRepository.RecentsRotationState(activityRotation))
+
+ whenever(previewPositionHelper.matrix).thenReturn(MATRIX)
+ whenever(previewPositionHelper.isOrientationChanged).thenReturn(isRotated)
+
+ assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .isEqualTo(MatrixScaling(MATRIX, isRotated))
+
+ verify(previewPositionHelper)
+ .updateThumbnailMatrix(
+ Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT),
+ thumbnailData,
+ CANVAS_WIDTH,
+ CANVAS_HEIGHT,
+ isLargeScreen,
+ activityRotation,
+ isRtl
+ )
+ }
+
+ companion object {
+ const val TASK_ID = 2
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ const val CANVAS_WIDTH = 300
+ const val CANVAS_HEIGHT = 600
+ val MATRIX =
+ Matrix().apply {
+ setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
+ }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
similarity index 98%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
index 414f8ca..12a94cf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep.task.util
+package com.android.quickstep.recents.usecase
import android.content.ComponentName
import android.content.Intent
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
index b78f871..754c9d1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -20,15 +20,19 @@
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
-import android.graphics.Rect
+import android.graphics.Matrix
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
@@ -48,8 +52,15 @@
private val taskViewData by lazy { TaskViewData(taskViewType) }
private val taskContainerData = TaskContainerData()
private val tasksRepository = FakeTasksRepository()
+ private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
private val systemUnderTest by lazy {
- TaskThumbnailViewModel(recentsViewData, taskViewData, taskContainerData, tasksRepository)
+ TaskThumbnailViewModel(
+ recentsViewData,
+ taskViewData,
+ taskContainerData,
+ tasksRepository,
+ mGetThumbnailPositionUseCase
+ )
}
private val tasks = (0..5).map(::createTaskWithId)
@@ -149,7 +160,6 @@
Snapshot(
backgroundColor = Color.rgb(2, 2, 2),
bitmap = expectedThumbnailData.thumbnail!!,
- drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
)
)
}
@@ -170,11 +180,38 @@
Snapshot(
backgroundColor = Color.rgb(2, 2, 2),
bitmap = expectedThumbnailData.thumbnail!!,
- drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
)
)
}
+ @Test
+ fun getSnapshotMatrix_MissingThumbnail() = runTest {
+ val taskId = 2
+ val recentTask = TaskThumbnail(taskId = taskId, isRunning = false)
+ val isRtl = true
+
+ whenever(mGetThumbnailPositionUseCase.run(taskId, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .thenReturn(MissingThumbnail)
+
+ systemUnderTest.bind(recentTask)
+ assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .isEqualTo(Matrix.IDENTITY_MATRIX)
+ }
+
+ @Test
+ fun getSnapshotMatrix_MatrixScaling() = runTest {
+ val taskId = 2
+ val recentTask = TaskThumbnail(taskId = taskId, isRunning = false)
+ val isRtl = true
+
+ whenever(mGetThumbnailPositionUseCase.run(taskId, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .thenReturn(MatrixScaling(MATRIX, isRotated = false))
+
+ systemUnderTest.bind(recentTask)
+ assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .isEqualTo(MATRIX)
+ }
+
private fun createTaskWithId(taskId: Int) =
Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
colorBackground = Color.argb(taskId, taskId, taskId, taskId)
@@ -191,5 +228,11 @@
companion object {
const val THUMBNAIL_WIDTH = 100
const val THUMBNAIL_HEIGHT = 200
+ const val CANVAS_WIDTH = 300
+ const val CANVAS_HEIGHT = 600
+ val MATRIX =
+ Matrix().apply {
+ setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
+ }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
index 40482c4..d0887df 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
@@ -23,9 +23,13 @@
import android.graphics.Matrix
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
+import com.android.quickstep.task.viewmodel.TaskOverlayViewModel.ThumbnailPositionState
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
@@ -53,7 +57,9 @@
)
private val recentsViewData = RecentsViewData()
private val tasksRepository = FakeTasksRepository()
- private val systemUnderTest = TaskOverlayViewModel(task, recentsViewData, tasksRepository)
+ private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
+ private val systemUnderTest =
+ TaskOverlayViewModel(task, recentsViewData, mGetThumbnailPositionUseCase, tasksRepository)
@Test
fun initialStateIsDisabled() = runTest {
@@ -87,7 +93,6 @@
Enabled(
isRealSnapshot = false,
thumbnail = null,
- thumbnailMatrix = Matrix.IDENTITY_MATRIX
)
)
}
@@ -107,7 +112,6 @@
Enabled(
isRealSnapshot = true,
thumbnail = thumbnailData.thumbnail,
- thumbnailMatrix = Matrix.IDENTITY_MATRIX
)
)
}
@@ -127,7 +131,6 @@
Enabled(
isRealSnapshot = false,
thumbnail = thumbnailData.thumbnail,
- thumbnailMatrix = Matrix.IDENTITY_MATRIX
)
)
}
@@ -147,14 +150,42 @@
Enabled(
isRealSnapshot = false,
thumbnail = thumbnailData.thumbnail,
- thumbnailMatrix = Matrix.IDENTITY_MATRIX
)
)
}
+ @Test
+ fun getThumbnailMatrix_MissingThumbnail() = runTest {
+ val isRtl = true
+
+ whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .thenReturn(MissingThumbnail)
+
+ assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .isEqualTo(ThumbnailPositionState(Matrix.IDENTITY_MATRIX, isRotated = false))
+ }
+
+ @Test
+ fun getThumbnailMatrix_MatrixScaling() = runTest {
+ val isRtl = true
+ val isRotated = true
+
+ whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .thenReturn(MatrixScaling(MATRIX, isRotated))
+
+ assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+ .isEqualTo(ThumbnailPositionState(MATRIX, isRotated))
+ }
+
companion object {
const val TASK_ID = 0
const val THUMBNAIL_WIDTH = 100
const val THUMBNAIL_HEIGHT = 200
+ const val CANVAS_WIDTH = 300
+ const val CANVAS_HEIGHT = 600
+ val MATRIX =
+ Matrix().apply {
+ setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
+ }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
index 0bf68eb..d66197a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
@@ -16,6 +16,7 @@
package com.android.quickstep.taskbar.customization
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator
import com.android.launcher3.taskbar.customization.TaskbarIconSpecs
import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator
@@ -32,7 +33,9 @@
class TaskbarSpecsEvaluatorTest {
private val taskbarFeatureEvaluator = mock<TaskbarFeatureEvaluator>()
- private val taskbarSpecsEvaluator = spy(TaskbarSpecsEvaluator(taskbarFeatureEvaluator))
+ private val taskbarActivityContext = mock<TaskbarActivityContext>()
+ private var taskbarSpecsEvaluator =
+ spy(TaskbarSpecsEvaluator(taskbarActivityContext, taskbarFeatureEvaluator, 0, 0))
@Test
fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumnInLandscape() {
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 298dd6c..f5d082d 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -288,6 +289,34 @@
assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
}
+ @Test
+ public void testSimpleOrientationTouchTransformer() {
+ final DisplayController displayController = mock(DisplayController.class);
+ doReturn(mInfo).when(displayController).getInfo();
+ final SimpleOrientationTouchTransformer transformer =
+ new SimpleOrientationTouchTransformer(getApplicationContext(), displayController);
+ final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.transform(move1, Surface.ROTATION_90);
+ // The position is transformed to 90 degree.
+ assertEquals(10, move1.getX(), 0f /* delta */);
+ assertEquals(NORMAL_SCREEN_SIZE.getWidth() - 100, move1.getY(), 0f /* delta */);
+
+ // If the touching state is specified, the position is still transformed to 90 degree even
+ // if the given rotation is changed.
+ final MotionEvent move2 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.updateTouchingOrientation(Surface.ROTATION_90);
+ transformer.transform(move2, Surface.ROTATION_0);
+ assertEquals(move1.getX(), move2.getX(), 0f /* delta */);
+ assertEquals(move1.getY(), move2.getY(), 0f /* delta */);
+
+ // If the touching state is cleared, it restores to use the given rotation.
+ final MotionEvent move3 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.clearTouchingOrientation();
+ transformer.transform(move3, Surface.ROTATION_0);
+ assertEquals(100, move3.getX(), 0f /* delta */);
+ assertEquals(10, move3.getY(), 0f /* delta */);
+ }
+
private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) {
Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight());
RotationUtils.rotateSize(displaySize, rotation);
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 5d00255..d049fbc 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -16,6 +16,8 @@
package com.android.quickstep;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertNull;
import static org.junit.Assert.assertEquals;
@@ -69,14 +71,14 @@
}
@Test
- public void onRecentTasksChanged_doesNotFetchTasks() {
+ public void onRecentTasksChanged_doesNotFetchTasks() throws Exception {
mRecentTasksList.onRecentTasksChanged();
verify(mockSystemUiProxy, times(0))
.getRecentTasks(anyInt(), anyInt());
}
@Test
- public void loadTasksInBackground_onlyKeys_noValidTaskDescription() {
+ public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception {
GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(
new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null);
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
@@ -91,7 +93,19 @@
}
@Test
- public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() {
+ public void loadTasksInBackground_GetRecentTasksException() throws Exception {
+ when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+ .thenThrow(new SystemUiProxy.GetRecentTasksException("task load failed"));
+
+ RecentTasksList.TaskLoadResult taskList = mRecentTasksList.loadTasksInBackground(
+ Integer.MAX_VALUE, -1, false);
+
+ assertThat(taskList.mRequestId).isEqualTo(-1);
+ assertThat(taskList).isEmpty();
+ }
+
+ @Test
+ public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception {
String taskDescription = "Wheeee!";
ActivityManager.RecentTaskInfo task1 = new ActivityManager.RecentTaskInfo();
task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
@@ -111,7 +125,7 @@
}
@Test
- public void loadTasksInBackground_freeformTask_createsDesktopTask() {
+ public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception {
ActivityManager.RecentTaskInfo[] tasks = {
createRecentTaskInfo(1 /* taskId */),
createRecentTaskInfo(4 /* taskId */),
@@ -134,7 +148,8 @@
}
@Test
- public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() {
+ public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask()
+ throws Exception {
ActivityManager.RecentTaskInfo[] tasks = {
createRecentTaskInfo(1 /* taskId */),
createRecentTaskInfo(4 /* taskId */),
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index ec245ee..c24e974 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -24,6 +24,7 @@
import androidx.test.filters.LargeTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -133,6 +134,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/349439239
public void testLaunchAppInSplitscreen_fromTaskbarAllApps() {
getTaskbar().openAllApps()
.getAppIcon(TEST_APP_NAME)
diff --git a/res/color-night-v31/material_color_surface.xml b/res/color-night-v31/material_color_surface.xml
deleted file mode 100644
index a645f24..0000000
--- a/res/color-night-v31/material_color_surface.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_bright.xml b/res/color-night-v31/material_color_surface_bright.xml
deleted file mode 100644
index f34ed6c..0000000
--- a/res/color-night-v31/material_color_surface_bright.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="24" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container.xml b/res/color-night-v31/material_color_surface_container.xml
deleted file mode 100644
index 002b88e..0000000
--- a/res/color-night-v31/material_color_surface_container.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_high.xml b/res/color-night-v31/material_color_surface_container_high.xml
deleted file mode 100644
index edd36fc..0000000
--- a/res/color-night-v31/material_color_surface_container_high.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="17" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_highest.xml b/res/color-night-v31/material_color_surface_container_highest.xml
deleted file mode 100644
index e54f953..0000000
--- a/res/color-night-v31/material_color_surface_container_highest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="22" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_low.xml b/res/color-night-v31/material_color_surface_container_low.xml
deleted file mode 100644
index 40f0d4c..0000000
--- a/res/color-night-v31/material_color_surface_container_low.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="10" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_lowest.xml b/res/color-night-v31/material_color_surface_container_lowest.xml
deleted file mode 100644
index 24f559b..0000000
--- a/res/color-night-v31/material_color_surface_container_lowest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_dim.xml b/res/color-night-v31/material_color_surface_dim.xml
deleted file mode 100644
index a645f24..0000000
--- a/res/color-night-v31/material_color_surface_dim.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_inverse.xml b/res/color-night-v31/material_color_surface_inverse.xml
deleted file mode 100644
index ac63072..0000000
--- a/res/color-night-v31/material_color_surface_inverse.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_variant.xml b/res/color-night-v31/material_color_surface_variant.xml
deleted file mode 100644
index a645f24..0000000
--- a/res/color-night-v31/material_color_surface_variant.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/popup_shade_first.xml b/res/color-night-v31/popup_shade_first.xml
index 83822a6..28995e3 100644
--- a/res/color-night-v31/popup_shade_first.xml
+++ b/res/color-night-v31/popup_shade_first.xml
@@ -12,7 +12,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/materialColorSurfaceContainer"/>
</selector>
diff --git a/res/color-v31/material_color_surface.xml b/res/color-v31/material_color_surface.xml
deleted file mode 100644
index b049851..0000000
--- a/res/color-v31/material_color_surface.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_bright.xml b/res/color-v31/material_color_surface_bright.xml
deleted file mode 100644
index b049851..0000000
--- a/res/color-v31/material_color_surface_bright.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container.xml b/res/color-v31/material_color_surface_container.xml
deleted file mode 100644
index b031c08..0000000
--- a/res/color-v31/material_color_surface_container.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_high.xml b/res/color-v31/material_color_surface_container_high.xml
deleted file mode 100644
index a996d51..0000000
--- a/res/color-v31/material_color_surface_container_high.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="92" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_highest.xml b/res/color-v31/material_color_surface_container_highest.xml
deleted file mode 100644
index e7a535a..0000000
--- a/res/color-v31/material_color_surface_container_highest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="90" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_low.xml b/res/color-v31/material_color_surface_container_low.xml
deleted file mode 100644
index b8fe01e..0000000
--- a/res/color-v31/material_color_surface_container_low.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="96" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_lowest.xml b/res/color-v31/material_color_surface_container_lowest.xml
deleted file mode 100644
index 25e8666..0000000
--- a/res/color-v31/material_color_surface_container_lowest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="100" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_dim.xml b/res/color-v31/material_color_surface_dim.xml
deleted file mode 100644
index e2d226f..0000000
--- a/res/color-v31/material_color_surface_dim.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="87" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_inverse.xml b/res/color-v31/material_color_surface_inverse.xml
deleted file mode 100644
index e189862..0000000
--- a/res/color-v31/material_color_surface_inverse.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_variant.xml b/res/color-v31/material_color_surface_variant.xml
deleted file mode 100644
index e2d226f..0000000
--- a/res/color-v31/material_color_surface_variant.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="87" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/popup_shade_first.xml b/res/color-v31/popup_shade_first.xml
index 1278bb4..be73698 100644
--- a/res/color-v31/popup_shade_first.xml
+++ b/res/color-v31/popup_shade_first.xml
@@ -13,7 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/materialColorSurfaceContainer"/>
</selector>
diff --git a/res/color/overview_button.xml b/res/color/overview_button.xml
index 1dd8da6..0b317bd 100644
--- a/res/color/overview_button.xml
+++ b/res/color/overview_button.xml
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:alpha="1"
- android:color="?androidprv:attr/materialColorOnSurface"
+ android:color="?attr/materialColorOnSurface"
android:state_enabled="true" />
<item
android:alpha="?android:disabledAlpha"
- android:color="?androidprv:attr/materialColorOnSurface"
+ android:color="?attr/materialColorOnSurface"
android:state_enabled="false" />
</selector>
\ No newline at end of file
diff --git a/res/color/popup_shade_first.xml b/res/color/popup_shade_first.xml
index 1278bb4..be73698 100644
--- a/res/color/popup_shade_first.xml
+++ b/res/color/popup_shade_first.xml
@@ -13,7 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/materialColorSurfaceContainer"/>
</selector>
diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
index e279fa0..be4765a 100644
--- a/res/drawable/add_item_dialog_background.xml
+++ b/res/drawable/add_item_dialog_background.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
- <solid android:color="@color/material_color_surface_container_highest" />
+ <solid android:color="?attr/materialColorSurfaceContainerHighest" />
<corners
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="?android:attr/dialogCornerRadius" />
diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml
index 1e7cff2..62927af 100644
--- a/res/drawable/all_apps_tabs_background.xml
+++ b/res/drawable/all_apps_tabs_background.xml
@@ -30,7 +30,7 @@
android:state_selected="false">
<shape android:shape="rectangle">
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
- <solid android:color="@color/material_color_surface_bright" />
+ <solid android:color="?attr/materialColorSurfaceBright" />
</shape>
</item>
@@ -39,7 +39,7 @@
android:state_selected="true">
<shape android:shape="rectangle">
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
- <solid android:color="@color/material_color_primary" />
+ <solid android:color="?attr/materialColorPrimary" />
</shape>
</item>
</selector>
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
index 379e0e5..a19465d 100644
--- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
+++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
@@ -15,8 +15,7 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle" >
- <solid android:color="?androidprv:attr/materialColorOutlineVariant"/>
+ <solid android:color="?attr/materialColorOutlineVariant"/>
<corners android:radius="@dimen/bottom_sheet_handle_corner_radius" />
</shape>
diff --git a/res/drawable/button_top_rounded_bordered_ripple.xml b/res/drawable/button_top_rounded_bordered_ripple.xml
index f5b6886..13959f6 100644
--- a/res/drawable/button_top_rounded_bordered_ripple.xml
+++ b/res/drawable/button_top_rounded_bordered_ripple.xml
@@ -25,7 +25,7 @@
android:topRightRadius="12dp"
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp" />
- <solid android:color="@color/material_color_surface_container_highest"/>
+ <solid android:color="?attr/materialColorSurfaceContainerHighest"/>
<stroke
android:width="2dp"
android:color="@color/button_bg"/>
diff --git a/res/drawable/cloud_download_24px.xml b/res/drawable/cloud_download_24px.xml
new file mode 100644
index 0000000..6f7c95a
--- /dev/null
+++ b/res/drawable/cloud_download_24px.xml
@@ -0,0 +1,11 @@
+<!-- GM3 icon cloud_download:vd_theme_24 -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M260,800Q169,800 104.5,737Q40,674 40,583Q40,505 87,444Q134,383 210,366Q227,294 295,229Q363,164 440,164Q473,164 496.5,187.5Q520,211 520,244L520,486L584,424L640,480L480,640L320,480L376,424L440,486L440,244Q364,258 322,317.5Q280,377 280,440L260,440Q202,440 161,481Q120,522 120,580Q120,638 161,679Q202,720 260,720L740,720Q782,720 811,691Q840,662 840,620Q840,578 811,549Q782,520 740,520L680,520L680,440Q680,392 658,350.5Q636,309 600,280L600,187Q674,222 717,290.5Q760,359 760,440L760,440L760,440Q829,448 874.5,499.5Q920,551 920,620Q920,695 867.5,747.5Q815,800 740,800L260,800ZM480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442L480,442L480,442Q480,442 480,442Q480,442 480,442Z"/>
+</vector>
diff --git a/res/drawable/ic_close_work_edu.xml b/res/drawable/ic_close_work_edu.xml
index f336eea..e4053e3 100644
--- a/res/drawable/ic_close_work_edu.xml
+++ b/res/drawable/ic_close_work_edu.xml
@@ -20,6 +20,6 @@
android:viewportWidth="960"
android:viewportHeight="960">
<path
- android:fillColor="@color/material_color_on_surface"
+ android:fillColor="?attr/materialColorOnSurface"
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
</vector>
diff --git a/res/drawable/ic_more_vert_dots.xml b/res/drawable/ic_more_vert_dots.xml
new file mode 100644
index 0000000..c4659f8
--- /dev/null
+++ b/res/drawable/ic_more_vert_dots.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_private_space_with_background.xml b/res/drawable/ic_private_space_with_background.xml
index cb37c9a..cc73f6d 100644
--- a/res/drawable/ic_private_space_with_background.xml
+++ b/res/drawable/ic_private_space_with_background.xml
@@ -13,14 +13,13 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:viewportWidth="48"
android:viewportHeight="48"
android:width="48dp"
android:height="48dp">
<path
android:pathData="M48 24A24 24 0 0 1 0 24A24 24 0 0 1 48 24Z"
- android:fillColor="?androidprv:attr/materialColorSurfaceContainerLowest" />
+ android:fillColor="?attr/materialColorSurfaceContainerLowest" />
<path
android:pathData="M24.0021 10.6641L13.3354 14.6641V22.7841C13.3354 29.5174 17.8821 35.7974 24.0021 37.3307C30.1221 35.7974 34.6688 29.5174 34.6688 22.7841V14.6641L24.0021 10.6641ZM32.0021 22.7841C32.0021 28.1174 28.6021 33.0507 24.0021 34.5574C19.4021 33.0507 16.0021 28.1307 16.0021 22.7841V16.5174L24.0021 13.5174L32.0021 16.5174V22.7841Z"
android:fillType="evenOdd"
diff --git a/res/drawable/icon_menu_arrow_background.xml b/res/drawable/icon_menu_arrow_background.xml
index 2eb1dfc..6345c2b 100644
--- a/res/drawable/icon_menu_arrow_background.xml
+++ b/res/drawable/icon_menu_arrow_background.xml
@@ -15,14 +15,13 @@
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:autoMirrored="true">
<gradient
android:type="linear"
android:angle="0"
android:startColor="#00000000"
android:centerX="0.25"
- android:centerColor="?androidprv:attr/materialColorSurfaceBright"
- android:endColor="?androidprv:attr/materialColorSurfaceBright" />
+ android:centerColor="?attr/materialColorSurfaceBright"
+ android:endColor="?attr/materialColorSurfaceBright" />
<corners android:radius="@dimen/dialogCornerRadius" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
index cfec2b1..12c4a82 100644
--- a/res/drawable/private_space_install_app_icon.xml
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -23,9 +23,9 @@
android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
<path
android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
- android:fillColor="@color/material_color_surface_container_lowest" />
+ android:fillColor="?attr/materialColorSurfaceContainerLowest" />
<path
android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
- android:fillColor="@color/material_color_on_surface" />
+ android:fillColor="?attr/materialColorOnSurface" />
</group>
</vector>
diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml
index e283d3f..ddd3042 100644
--- a/res/drawable/rounded_action_button.xml
+++ b/res/drawable/rounded_action_button.xml
@@ -7,7 +7,7 @@
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
- ~ Unless required by applicable law or agreed to in writing, software
+ ~ Unless required by applicable law or agreed to in writing, soft]ware
~ 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
@@ -16,13 +16,12 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="@color/material_color_surface_container_low" />
+ <solid android:color="?attr/materialColorSurfaceContainerLow" />
<corners android:radius="@dimen/rounded_button_radius" />
<stroke
android:width="1dp"
- android:color="@color/material_color_surface_container_low" />
+ android:color="?attr/materialColorSurfaceContainerLow" />
<padding
android:left="@dimen/rounded_button_padding"
android:right="@dimen/rounded_button_padding" />
diff --git a/res/drawable/work_card.xml b/res/drawable/work_card.xml
index 9bf2b8d..01ec947 100644
--- a/res/drawable/work_card.xml
+++ b/res/drawable/work_card.xml
@@ -17,7 +17,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/material_color_surface_container_highest" />
+ <solid android:color="?attr/materialColorSurfaceContainerHighest" />
<corners android:radius="@dimen/work_edu_card_radius" />
</shape>
diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml
index 9c0f129..e68f0f3 100644
--- a/res/layout/private_space_header.xml
+++ b/res/layout/private_space_header.xml
@@ -43,6 +43,7 @@
android:layout_height="@dimen/ps_header_image_height"
android:background="@drawable/ps_settings_background"
android:src="@drawable/ic_ps_settings"
+ android:visibility="gone"
android:contentDescription="@string/ps_container_settings" />
<LinearLayout
android:id="@+id/ps_lock_unlock_button"
@@ -60,7 +61,7 @@
android:layout_marginBottom="@dimen/ps_lock_icon_margin_bottom"
android:importantForAccessibility="no"
android:src="@drawable/ic_lock"
- app:tint="@color/material_color_primary_fixed_dim"
+ app:tint="?attr/materialColorPrimaryFixedDim"
android:scaleType="center"/>
<TextView
android:id="@+id/lock_text"
@@ -68,10 +69,12 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/ps_lock_icon_text_margin_start_expanded"
android:layout_marginEnd="@dimen/ps_lock_icon_text_margin_end_expanded"
- android:textColor="@color/material_color_on_primary_fixed"
+ android:textColor="?attr/materialColorOnPrimaryFixed"
android:textSize="14sp"
android:text="@string/ps_container_lock_title"
+ android:maxLines="1"
android:visibility="gone"
+ android:alpha="0"
style="@style/TextHeadline"/>
</LinearLayout>
</LinearLayout>
diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml
index 887efb8..1f41680 100644
--- a/res/layout/widgets_two_pane_sheet_paged_view.xml
+++ b/res/layout/widgets_two_pane_sheet_paged_view.xml
@@ -55,18 +55,39 @@
android:clipToOutline="true"
android:orientation="vertical">
- <FrameLayout
+ <LinearLayout
android:id="@+id/search_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:orientation="horizontal"
android:background="?attr/widgetPickerPrimarySurfaceColor"
- android:clipToPadding="false"
- android:elevation="0.1dp"
- android:paddingBottom="8dp"
+ android:gravity="center_vertical"
launcher:layout_sticky="true">
+ <FrameLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:clipToPadding="false"
+ android:elevation="0.1dp"
+ android:paddingBottom="8dp">
- <include layout="@layout/widgets_search_bar" />
- </FrameLayout>
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <ImageButton
+ android:id="@+id/widget_picker_widget_options_menu"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginBottom="8dp"
+ android:layout_gravity="bottom"
+ android:background="@drawable/full_rounded_transparent_ripple"
+ android:contentDescription="@string/widget_picker_widget_options_button_description"
+ android:padding="12dp"
+ android:src="@drawable/ic_more_vert_dots"
+ android:visibility="gone"
+ android:tint="?attr/widgetPickerWidgetOptionsMenuColor" />
+ </LinearLayout>
<FrameLayout
android:layout_width="match_parent"
diff --git a/res/layout/widgets_two_pane_sheet_recyclerview.xml b/res/layout/widgets_two_pane_sheet_recyclerview.xml
index f3d3b16..c6b3b74 100644
--- a/res/layout/widgets_two_pane_sheet_recyclerview.xml
+++ b/res/layout/widgets_two_pane_sheet_recyclerview.xml
@@ -39,19 +39,40 @@
android:clipToOutline="true"
android:orientation="vertical">
- <FrameLayout
+ <LinearLayout
android:id="@+id/search_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
android:background="?attr/widgetPickerPrimarySurfaceColor"
- android:clipToPadding="false"
- android:elevation="0.1dp"
- android:paddingBottom="16dp"
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
launcher:layout_sticky="true">
+ <FrameLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:clipToPadding="false"
+ android:elevation="0.1dp"
+ android:paddingBottom="16dp">
- <include layout="@layout/widgets_search_bar" />
- </FrameLayout>
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <ImageButton
+ android:id="@+id/widget_picker_widget_options_menu"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginBottom="16dp"
+ android:layout_gravity="bottom"
+ android:background="@drawable/full_rounded_transparent_ripple"
+ android:contentDescription="@string/widget_picker_widget_options_button_description"
+ android:padding="12dp"
+ android:src="@drawable/ic_more_vert_dots"
+ android:visibility="gone"
+ android:tint="?attr/widgetPickerWidgetOptionsMenuColor" />
+ </LinearLayout>
<FrameLayout
android:layout_width="match_parent"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 90e784d..1a8f8e2 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
<string name="home_screen" msgid="5629429142036709174">"Tuis"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stel <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as verstektuisskermapp in Instellings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Verdeelde skerm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programinligting vir %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruikinstellings vir %1$s"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 1289b8c..80447c5 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ምግብሮች በደህንነቱ የተጠበቀ ሁኔታ ተሰናክለዋል"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"አቋራጭ አይገኝም"</string>
<string name="home_screen" msgid="5629429142036709174">"መነሻ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"በቅንብሮች ውስጥ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ን እንደ ነባሪ የHome መተግበሪያ ያቀናብሩ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"የተከፈለ ማያ ገፅ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"የመተግበሪያ መረጃ ለ%1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"የ%1$s የአጠቃቀም ቅንብሮች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 37d6fba..4eee121 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات غير مفعّلة في الوضع الآمن"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
<string name="home_screen" msgid="5629429142036709174">"الشاشة الرئيسية"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"يمكن ضبط \"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>\" كتطبيق الشاشة الرئيسية التلقائي من خلال \"الإعدادات\""</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"تقسيم الشاشة"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"معلومات تطبيق %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"إعدادات استخدام \"%1$s\""</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index e983ce8..52ec7ea 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ৱিজেটবোৰক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"শ্বৰ্টকাট নাই"</string>
<string name="home_screen" msgid="5629429142036709174">"গৃহ স্ক্ৰীন"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ছেটিঙত <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ক ডিফ’ল্ট গৃহপৃষ্ঠা এপ্ হিচাপে ছেট কৰক"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"বিভাজিত স্ক্ৰীন"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sৰ বাবে এপৰ তথ্য"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sৰ বাবে ব্যৱহাৰৰ ছেটিং"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index f390c7c..6c1cc46 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
<string name="home_screen" msgid="5629429142036709174">"Əsas səhifə"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ayarlarda <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> tətbiqini defolt əsas ekran tətbiqi kimi ayarlayın"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekran bölünməsi"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilə bağlı tətbiq məlumatı"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s üzrə istifadə ayarları"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index fc71eeb..24328cf 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Podesite <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao podrazumevanu početnu aplikaciju u Podešavanjima"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji za: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Podešavanja potrošnje za %1$s"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 4589125..ebbb378 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Віджэты адключаны ў Бяспечным рэжыме"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недаступны"</string>
<string name="home_screen" msgid="5629429142036709174">"Галоўны экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Зрабіць <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> стандартнай праграмай для галоўнага экрана, перайшоўшы ў Налады"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Падзелены экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Інфармацыя пра праграму для: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s: налады выкарыстання"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 643c24e..d5d948e 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Приспособленията са деактивирани в безопасния режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Няма достъп до прекия път"</string>
<string name="home_screen" msgid="5629429142036709174">"Начален екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"От настройките задайте <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> като основното приложение за начален екран"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделен екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Информация за приложението за %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки за използването на %1$s"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 7e10a0e..cf75fb58 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"সুরক্ষিত মোডে উইজেট নিষ্ক্রিয় থাকে"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"শর্টকাটগুলি অনুপলব্ধ"</string>
<string name="home_screen" msgid="5629429142036709174">"হোম"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"সেটিংসে গিয়ে <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> অ্যাপকে ডিফল্ট হোম অ্যাপ হিসেবে সেট করুন"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"স্প্লিট স্ক্রিন"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-এর জন্য অ্যাপ সম্পর্কিত তথ্য"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-এর জন্য ব্যবহারের সেটিংস"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 10aa86a..1758c39 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Postavite <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao zadanu aplikaciju za početni ekran u Postavkama"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke korištenja za: %1$s"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 3d2ff4a..bf578f5 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inici"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Defineix <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> com a aplicació d\'inici predeterminada a Configuració"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informació de l\'aplicació %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configuració d\'ús de %1$s"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 14a3583..9a8fc6f 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
<string name="home_screen" msgid="5629429142036709174">"Domů"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastavit <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> jako výchozí vstupní aplikaci v Nastavení"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělit obrazovku"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavení využití pro aplikaci %1$s"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index f18e1e8..208c2ef 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskærm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Angiv <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standardstartapp i Indstillinger"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Opdel skærm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinfo for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Indstillinger for brug af %1$s"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 9a4e5e2..380030b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
<string name="home_screen" msgid="5629429142036709174">"Startbildschirm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> in den Einstellungen als Stand-Start-App festlegen"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Splitscreen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App-Info für %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nutzungseinstellungen für %1$s"</string>
@@ -37,7 +36,7 @@
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Dieses App-Paar wird auf diesem Gerät nicht unterstützt"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Gerät aufklappen, um dieses App-Paar zu verwenden"</string>
<string name="app_pair_not_available" msgid="3556767440808032031">"App-Paar nicht verfügbar"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"Zum Verschieben des Widgets berühren und halten"</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"Zum Verschieben des Widgets gedrückt halten"</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Doppeltippen und halten, um ein Widget zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breit und %2$d hoch"</string>
@@ -75,7 +74,7 @@
<string name="label_application" msgid="8531721983832654978">"App"</string>
<string name="all_apps_label" msgid="5015784846527570951">"Alle Apps"</string>
<string name="notifications_header" msgid="1404149926117359025">"Benachrichtigungen"</string>
- <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Zum Verschieben einer Verknüpfung berühren und halten"</string>
+ <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Zum Verschieben einer Verknüpfung gedrückt halten"</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Doppeltippen und halten, um eine Verknüpfung zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
<string name="out_of_space" msgid="6455557115204099579">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 840ff5b..e86ebae 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Τα γραφικά στοιχεία απενεργοποιήθηκαν στην ασφαλή λειτουργία"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Η συντόμευση δεν είναι διαθέσιμη"</string>
<string name="home_screen" msgid="5629429142036709174">"Αρχική οθόνη"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ορίστε το <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ως την προεπιλεγμένη εφαρμογή αρχικής οθόνης στις Ρυθμίσεις"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Διαχωρισμός οθόνης"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Πληροφορίες εφαρμογής για %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Ρυθμίσεις χρήσης για %1$s"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 4deacb6..50c5976 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 4deacb6..50c5976 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 4deacb6..50c5976 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index e734744..5879129 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Pantalla principal"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Establece <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como la app de inicio predeterminada en Configuración"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la app de %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración del uso de %1$s"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 3e0e516..8d384ac 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo Seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Define <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como aplicación de inicio predeterminada en Ajustes"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Ajustes de uso para %1$s"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index bab7da3..44448a6 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Turvarežiimis on vidinad keelatud"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Otsetee pole saadaval"</string>
<string name="home_screen" msgid="5629429142036709174">"Avakuva"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Määrake rakendus <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> seadetes avakuva vaikerakenduseks"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jagatud ekraanikuva"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Rakenduse teave: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Kasutuse seaded: %1$s"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index b8017d6..6fc4cf4 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
<string name="home_screen" msgid="5629429142036709174">"Orri nagusia"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ezarri <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> hasierako aplikazio lehenetsi gisa ezarpenetan"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantaila zatitzea"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s aplikazioari buruzko informazioa"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s aplikazioaren erabilera-ezarpenak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 5cbce53..fffce13 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ابزارکها در حالت ایمن غیرفعال هستند"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"میانبر دردسترس نیست"</string>
<string name="home_screen" msgid="5629429142036709174">"صفحه اصلی"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"تنظیم <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> بهعنوان برنامه صفحه اصلی پیشفرض در «تنظیمات»"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"صفحهٔ دونیمه"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"اطلاعات برنامه %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"تنظیمات مصرف برای %1$s"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 181c89e..310053e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
<string name="home_screen" msgid="5629429142036709174">"Etusivu"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Valitse <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> oletusaloitusnäyttösovellukseksi asetuksissa"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jaettu näyttö"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Sovellustiedot: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Käyttöasetus tälle: %1$s"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index f95f26d..11b2c01 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Définissez <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> comme appli d\'accueil par défaut dans les paramètres"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran divisé"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Renseignements sur l\'appli pour %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4a7c7fb..b4c5ec6 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Définissez <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> comme application d\'accueil par défaut dans Paramètres"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Infos sur l\'appli pour %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 0430a9a..ac72802 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Define <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como aplicación de inicio predeterminada en Configuración"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información da aplicación para %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración de uso para %1$s"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 3c248db..74747d0 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"સુરક્ષિત મોડમાં વિજેટ્સ અક્ષમ કર્યા"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"શૉર્ટકટ ઉપલબ્ધ નથી"</string>
<string name="home_screen" msgid="5629429142036709174">"હોમ સ્ક્રીન"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"સેટિંગમાં <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ને ડિફૉલ્ટ હોમ ઍપ તરીકે સેટ કરો"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"સ્ક્રીનને વિભાજિત કરો"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s માટે ઍપ માહિતી"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sના વપરાશ સંબંધિત સેટિંગ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 50d4762..6071935 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
<string name="home_screen" msgid="5629429142036709174">"होम स्क्रीन"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"सेटिंग में जाकर, <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> को डिफ़ॉल्ट होम ऐप्लिकेशन के तौर पर सेट करें"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s के लिए ऐप्लिकेशन की जानकारी"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s के लिए खर्च की सेटिंग"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 7a1fd94..cf7a91a 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni zaslon"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Postavite aplikaciju <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao zadanu aplikaciju početnog zaslona u postavkama"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke upotrebe za %1$s"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 3b556da..f306110 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
<string name="home_screen" msgid="5629429142036709174">"Kezdőképernyő"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"A(z) <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> appot a Beállításokban adhatja meg alapértelmezett kezdőalkalmazásként."</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Osztott képernyő"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Alkalmazásinformáció a következőhöz: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"A(z) %1$s használati beállításai"</string>
@@ -188,7 +187,7 @@
<string name="remote_action_failed" msgid="1383965239183576790">"Sikertelen: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privát terület"</string>
<string name="private_space_secondary_label" msgid="9203933341714508907">"Koppintson a beállításhoz vagy a megnyitáshoz"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Privát terület"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Privát"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Privát terület beállításai"</string>
<string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privát, feloldott."</string>
<string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privát, zárolt."</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 491ba05..2d345f2 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Վիջեթներն անջատված են անվտանգ ռեժիմում"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Դյուրանցումն անհասանելի է"</string>
<string name="home_screen" msgid="5629429142036709174">"Հիմնական էկրան"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Սահմանել <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> գործարկիչը որպես մեկնարկի կանխադրված հավելված Կարգավորումներում"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Տրոհել էկրանը"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Տեղեկություններ %1$s հավելվածի մասին"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Օգտագործման կարգավորումներ (%1$s)"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 1335385..9ced9f4 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
<string name="home_screen" msgid="5629429142036709174">"Layar utama"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Jadikan <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sebagai aplikasi layar utama default di Setelan"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Layar terpisah"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Info aplikasi untuk %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Setelan penggunaan untuk %1$s"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 4865264..2ab7817 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
<string name="home_screen" msgid="5629429142036709174">"Heim"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stilltu <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sem sjálfgefið heimaforrit í stillingunum"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skipta skjá"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Upplýsingar um forrit fyrir %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Notkunarstillingar fyrir %1$s"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 41b4b60..a59de6c 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Imposta <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> come app iniziale predefinita nelle Impostazioni"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Schermo diviso"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informazioni sull\'app %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Impostazioni di utilizzo per %1$s"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ae71edd..89a0c8a 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ווידג\'טים מושבתים במצב בטוח"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"קיצור הדרך אינו זמין"</string>
<string name="home_screen" msgid="5629429142036709174">"בית"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"הגדרה של <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> כאפליקציית הבית ב\'הגדרות\'"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"מסך מפוצל"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"פרטים על האפליקציה %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"הגדרות שימוש ב-%1$s"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index c64d335..b48c7be 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"セーフモードではウィジェットは無効です"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ショートカットは使用できません"</string>
<string name="home_screen" msgid="5629429142036709174">"ホーム"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"[設定] で <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> をデフォルトのホームアプリに設定します"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割画面"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s のアプリ情報"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s の使用設定"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index bcced9a..76b8b5d 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"უსაფრთხო რეჟიმში ვიჯეტი გამორთულია"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"მალსახმობი მიუწვდომელია"</string>
<string name="home_screen" msgid="5629429142036709174">"მთავარი გვერდი"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>-ის დაყენება ნაგულისხმევ Home აპად პარამეტრებში"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ეკრანის გაყოფა"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-ის აპის ინფო"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"გამოყენების პარამეტრები %1$s-ისთვის"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 62bb983..95d4420 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Қауіпсіз режимде виджеттер өшіріледі"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Таңбаша қолжетімді емес"</string>
<string name="home_screen" msgid="5629429142036709174">"Негізгі экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Параметрлерден <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> қолданбасын әдепкі негізгі экран қолданбасы ретінде орнатыңыз."</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлу"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s қолданбасы туралы ақпарат"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s пайдалану параметрлері"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index b733699..5c71276 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"បានបិទធាតុក្រាហ្វិកក្នុងរបៀបសុវត្ថិភាព"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ផ្លូវកាត់មិនអាចប្រើបានទេ"</string>
<string name="home_screen" msgid="5629429142036709174">"អេក្រង់ដើម"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"កំណត់ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ជាកម្មវិធីអេក្រង់ដើមលំនាំដើមនៅក្នុង \"ការកំណត់\""</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"មុខងារបំបែកអេក្រង់"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ព័ត៌មានកម្មវិធីសម្រាប់ %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"ការកំណត់ការប្រើប្រាស់សម្រាប់ %1$s"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 16b39f8..931ca30 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ವಿಜೆಟ್ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ಶಾರ್ಟ್ಕಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="home_screen" msgid="5629429142036709174">"ಹೋಮ್"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ಅನ್ನು ಡೀಫಾಲ್ಟ್ ಹೋಮ್ ಆ್ಯಪ್ ಆಗಿ ಸೆಟ್ ಮಾಡಿ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ಗಾಗಿ ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ಗೆ ಸಂಬಂಧಿಸಿದ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1d5c58f..9f64cfb 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"안전 모드에서 위젯 사용 중지됨"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"바로가기를 사용할 수 없음"</string>
<string name="home_screen" msgid="5629429142036709174">"홈"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"설정에서 <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> 런처를 기본 홈 앱으로 설정하세요."</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"화면 분할"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 앱 정보"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s의 사용량 설정"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 6f01db3..3e67c36 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеттер Коопсуз режимде өчүрүлгөн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кыска жол жок"</string>
<string name="home_screen" msgid="5629429142036709174">"Башкы экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Параметрлерден <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> жүргүзгүчүн демейки башкы бет колдонмосу катары коюу"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлүү"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s колдонмосу жөнүндө маалымат"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s колдонмосун пайдалануу параметрлери"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 7ec103b..e0cd3ee 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ວິດເຈັດຖືກປິດໃນ Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
<string name="home_screen" msgid="5629429142036709174">"ໂຮມສະກຣີນ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ຕັ້ງ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ເປັນແອັບໂຮມເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ແບ່ງໜ້າຈໍ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ຂໍ້ມູນແອັບສຳລັບ %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"ການຕັ້ງຄ່າການນຳໃຊ້ສຳລັບ %1$s"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 1112474..9846525 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
<string name="home_screen" msgid="5629429142036709174">"Pagrindinis"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nustatykite „<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>“ kaip numatytąją pagrindinę programą skiltyje „Nustatymai“"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"„%1$s“ naudojimo nustatymai"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 4f1a92c..49f7ffe 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
<string name="home_screen" msgid="5629429142036709174">"Sākums"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Iestatiet <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kā noklusējuma sākuma lietotni iestatījumos."</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Sadalīt ekrānu"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s: informācija par lietotni"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Lietojuma iestatījumi: %1$s"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index d984ca0..2bfa089 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Додатоците се оневозможени во безбеден режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кратенката не е достапна"</string>
<string name="home_screen" msgid="5629429142036709174">"Почетен екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Поставете <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> да биде стандардна апликација за почетен екран во „Поставки“"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Поделен екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Податоци за апликација за %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Поставки за користење за %1$s"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index ea8849e..a2babd5 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"സുരക്ഷിത മോഡിൽ വിജറ്റുകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"കുറുക്കുവഴി ലഭ്യമല്ല"</string>
<string name="home_screen" msgid="5629429142036709174">"ഹോം"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ക്രമീകരണത്തിൽ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> എന്നത് ഡിഫോൾട്ട് ഹോം ആപ്പായി സജ്ജീകരിക്കുക"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"സ്ക്രീൻ വിഭജന മോഡ്"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s എന്നതിനുള്ള ഉപയോഗ ക്രമീകരണം"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 8554717..93a1c9c 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Товчлол алга"</string>
<string name="home_screen" msgid="5629429142036709174">"Нүүр"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>-г Тохиргоонд өгөгдмөл үндсэн аппаар тохируулна уу"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Дэлгэцийг хуваах"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-н аппын мэдээлэл"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-н ашиглалтын тохиргоо"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index dcdf25c..c084fab 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नाही"</string>
<string name="home_screen" msgid="5629429142036709174">"होम"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"सेटिंग्ज मध्ये <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> हे डीफॉल्ट होम अॅप म्हणून सेट करा"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s साठी ॲपशी संबंधित माहिती"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s साठी वापरासंबंधित सेटिंग्ज"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 22ee2cb..72d92e2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dilumpuhkan dalam mod Selamat"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
<string name="home_screen" msgid="5629429142036709174">"Rumah"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Tetapkan <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sebagai apl skrin utama lalai dalam Tetapan"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skrin pisah"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Maklumat apl untuk %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Tetapan penggunaan sebanyak %1$s"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index bca1c6e..194ece7 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"လုံခြုံရေး မုဒ်ထဲမှာ ဝီဂျက်များကို ပိတ်ထား"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ဖြတ်လမ်း မရနိုင်ပါ"</string>
<string name="home_screen" msgid="5629429142036709174">"ပင်မစာမျက်နှာ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ဆက်တင်များတွင် <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ကို မူရင်းပင်မစာမျက်နှာအက်ပ်အဖြစ် သတ်မှတ်ရန်"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s အတွက် အက်ပ်အချက်အလက်"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s အတွက် အသုံးပြုမှုဆက်တင်များ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 1440443..a9e6c5d 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskjerm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Angi <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standard startsideapp i innstillingene"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delt skjerm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformasjon for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Bruksinnstillinger for %1$s"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 52613cb..7880c36 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"सुरक्षित मोडमा विगेटहरू अक्षम गरियो"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"सर्टकट उपलब्ध छैन"</string>
<string name="home_screen" msgid="5629429142036709174">"होम"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"सेटिङमा गई <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> लाई डिफल्ट होम एपका रूपमा सेट गर्नुहोस्"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रिन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s का हकमा एपसम्बन्धी जानकारी"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s को प्रयोगसम्बन्धी सेटिङ"</string>
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index 2688b83..07c450e 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -57,39 +57,4 @@
@android:color/system_accent1_200</color>
<color name="work_fab_icon_color">
@android:color/system_accent1_900</color>
-
- <color name="material_color_on_secondary_fixed_variant">@android:color/system_accent2_700</color>
- <color name="material_color_on_tertiary_fixed_variant">@android:color/system_accent3_700</color>
- <color name="material_color_on_primary_fixed_variant">@android:color/system_accent1_700</color>
- <color name="material_color_on_secondary_container">@android:color/system_accent2_100</color>
- <color name="material_color_on_tertiary_container">@android:color/system_accent3_100</color>
- <color name="material_color_on_primary_container">@android:color/system_accent1_100</color>
- <color name="material_color_secondary_fixed_dim">@android:color/system_accent2_200</color>
- <color name="material_color_on_error_container">#FFDAD5</color>
- <color name="material_color_on_secondary_fixed">@android:color/system_accent2_900</color>
- <color name="material_color_on_surface_inverse">@android:color/system_neutral1_900</color>
- <color name="material_color_tertiary_fixed_dim">@android:color/system_accent3_200</color>
- <color name="material_color_on_tertiary_fixed">@android:color/system_accent3_900</color>
- <color name="material_color_primary_fixed_dim">@android:color/system_accent1_200</color>
- <color name="material_color_secondary_container">@android:color/system_accent2_700</color>
- <color name="material_color_error_container">#930001</color>
- <color name="material_color_on_primary_fixed">@android:color/system_accent1_900</color>
- <color name="material_color_primary_inverse">@android:color/system_accent1_600</color>
- <color name="material_color_secondary_fixed">@android:color/system_accent2_100</color>
- <color name="material_color_tertiary_container">@android:color/system_accent3_700</color>
- <color name="material_color_tertiary_fixed">@android:color/system_accent3_100</color>
- <color name="material_color_primary_container">@android:color/system_accent1_700</color>
- <color name="material_color_on_background">@android:color/system_neutral1_800</color>
- <color name="material_color_primary_fixed">@android:color/system_accent1_100</color>
- <color name="material_color_on_secondary">@android:color/system_accent2_800</color>
- <color name="material_color_on_tertiary">@android:color/system_accent3_800</color>
- <color name="material_color_on_error">#690001</color>
- <color name="material_color_on_surface_variant">@android:color/system_neutral2_200</color>
- <color name="material_color_outline">@android:color/system_neutral2_400</color>
- <color name="material_color_outline_variant">@android:color/system_neutral2_700</color>
- <color name="material_color_on_primary">@android:color/system_accent1_800</color>
- <color name="material_color_on_surface">@android:color/system_neutral1_100</color>
- <color name="material_color_primary">@android:color/system_accent1_200</color>
- <color name="material_color_secondary">@android:color/system_accent2_200</color>
- <color name="material_color_tertiary">@android:color/system_accent3_200</color>
</resources>
\ No newline at end of file
diff --git a/res/values-night-v34/colors.xml b/res/values-night-v34/colors.xml
index af28119..abce763 100644
--- a/res/values-night-v34/colors.xml
+++ b/res/values-night-v34/colors.xml
@@ -27,4 +27,7 @@
@android:color/system_on_surface_dark</color>
<color name="widget_cell_subtitle_color_dark">
@android:color/system_on_surface_variant_dark</color>
+ <color name="widget_picker_menu_options_color_dark">
+ @android:color/system_on_surface_variant_dark
+ </color>
</resources>
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
deleted file mode 100644
index 95b3a63..0000000
--- a/res/values-night/colors.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2023, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
- <color name="material_color_on_secondary_fixed_variant">#3F4759</color>
- <color name="material_color_on_tertiary_fixed_variant">#583E5B</color>
- <color name="material_color_surface_container_lowest">#0D0E11</color>
- <color name="material_color_on_primary_fixed_variant">#2B4678</color>
- <color name="material_color_on_secondary_container">#DBE2F9</color>
- <color name="material_color_on_tertiary_container">#FBD7FC</color>
- <color name="material_color_surface_container_low">#1B1B1F</color>
- <color name="material_color_on_primary_container">#D8E2FF</color>
- <color name="material_color_secondary_fixed_dim">#BFC6DC</color>
- <color name="material_color_on_error_container">#FFDAD5</color>
- <color name="material_color_on_secondary_fixed">#141B2C</color>
- <color name="material_color_on_surface_inverse">#1B1B1F</color>
- <color name="material_color_tertiary_fixed_dim">#DEBCDF</color>
- <color name="material_color_on_tertiary_fixed">#29132D</color>
- <color name="material_color_primary_fixed_dim">#ADC6FF</color>
- <color name="material_color_secondary_container">#3F4759</color>
- <color name="material_color_error_container">#930001</color>
- <color name="material_color_on_primary_fixed">#001A41</color>
- <color name="material_color_primary_inverse">#445E91</color>
- <color name="material_color_secondary_fixed">#DBE2F9</color>
- <color name="material_color_surface_inverse">#FAF9FD</color>
- <color name="material_color_surface_variant">#44474F</color>
- <color name="material_color_tertiary_container">#583E5B</color>
- <color name="material_color_tertiary_fixed">#FBD7FC</color>
- <color name="material_color_primary_container">#2B4678</color>
- <color name="material_color_on_background">#E3E2E6</color>
- <color name="material_color_primary_fixed">#D8E2FF</color>
- <color name="material_color_on_secondary">#293041</color>
- <color name="material_color_on_tertiary">#402843</color>
- <color name="material_color_surface_dim">#121316</color>
- <color name="material_color_surface_bright">#38393C</color>
- <color name="material_color_on_error">#690001</color>
- <color name="material_color_surface">#121316</color>
- <color name="material_color_surface_container_high">#292A2D</color>
- <color name="material_color_surface_container_highest">#343538</color>
- <color name="material_color_on_surface_variant">#C4C6D0</color>
- <color name="material_color_outline">#72747D</color>
- <color name="material_color_outline_variant">#444746</color>
- <color name="material_color_on_primary">#102F60</color>
- <color name="material_color_on_surface">#E3E2E6</color>
- <color name="material_color_surface_container">#1F1F23</color>
- <color name="material_color_primary">#ADC6FF</color>
- <color name="material_color_secondary">#BFC6DC</color>
- <color name="material_color_tertiary">#DEBCDF</color>
-</resources>
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index c95722f..06f0eee 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -26,9 +26,64 @@
<item name="android:backgroundDimEnabled">true</item>
</style>
- <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
+ <style name="DynamicColorsBaseLauncherTheme" parent="@style/BaseLauncherTheme">
+ <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+ <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+ <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+ <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
+ <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
+ <item name="materialColorOnError">@color/system_on_error_dark</item>
+ <item name="materialColorSurface">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
+ <item name="materialColorOutline">@color/system_outline_dark</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorPrimary">@color/system_primary_dark</item>
+ <item name="materialColorSecondary">@color/system_secondary_dark</item>
+ <item name="materialColorTertiary">@color/system_tertiary_dark</item>
+ <item name="materialColorError">@color/system_error_dark</item>
+ </style>
+
+ <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowAnimationStyle">@android:style/Animation</item>
+
+ <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="pageIndicatorDotColor">@color/page_indicator_dot_color_dark</item>
</style>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 90dcd46..7b3f563 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgezet in veilige modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
<string name="home_screen" msgid="5629429142036709174">"Startscherm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stel <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> in als de standaard startscherm-app in Instellingen"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gesplitst scherm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App-info voor %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruiksinstellingen voor %1$s"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 6d2f87e..33b7664 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ନିରାପଦ ମୋଡରେ ୱିଜେଟ୍ ଅକ୍ଷମ କରାଗଲା"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ଶର୍ଟକଟ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="home_screen" msgid="5629429142036709174">"ହୋମ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ସେଟିଂସରେ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>କୁ ଡିଫଲ୍ଟ Home ଆପ ଭାବରେ ସେଟ କରନ୍ତୁ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ସ୍କ୍ରିନକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ପାଇଁ ଆପ ସୂଚନା"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ପାଇଁ ବ୍ୟବହାର ସେଟିଂସ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 8cb5515..71c25eb 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜੇਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="home_screen" msgid="5629429142036709174">"ਮੁੱਖ ਪੰਨਾ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ਨੂੰ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਹੋਮ ਐਪ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ਲਈ ਐਪ ਜਾਣਕਾਰੀ"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ਲਈ ਵਰਤੋਂ ਸੈਟਿੰਗਾਂ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 685b7d9..587c40f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
<string name="home_screen" msgid="5629429142036709174">"Ekran główny"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Otwórz Ustawienia i ustaw aplikację <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> jako domyślną aplikację ekranu głównego"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podziel ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacje o aplikacji: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s – ustawienia użycia"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 14a3cf5..67d93de 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
<string name="home_screen" msgid="5629429142036709174">"Página inicial"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Defina a app <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como a app inicial predefinida nas Definições"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecrã dividido"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações da app para %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Definições de utilização para %1$s"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 4c4b13b..4302588 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
<string name="home_screen" msgid="5629429142036709174">"Início"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Definir <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como app de início padrão nas Configurações"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Tela dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configurações de uso de %1$s"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 256fa60..d9e0413 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
<string name="home_screen" msgid="5629429142036709174">"Pagina de pornire"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Setează <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> drept aplicație ecran de pornire prestabilită, în Setări"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecran împărțit"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informații despre aplicație pentru %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Setări de utilizare pentru %1$s"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 402143d..64a21f7 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеты отключены в безопасном режиме"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недоступен"</string>
<string name="home_screen" msgid="5629429142036709174">"Главный экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Установить \"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>\" в качестве приложения главного экрана по умолчанию в настройках"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделить экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Сведения о приложении \"%1$s\""</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки использования приложения \"%1$s\""</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 455f18b..71efc03 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"සුරක්ෂිත ආකාරය තුළ විජටය අබල කරන ලදි"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"කෙටි මග ලබා ගත නොහැකිය"</string>
<string name="home_screen" msgid="5629429142036709174">"මුල් පිටුව"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> සැකසීම් තුළ පෙරනිමි මුල් පිටුව යෙදුම ලෙස සකසන්න"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"බෙදුම් තිරය"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s සඳහා යෙදුම් තතු"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s සඳහා භාවිත සැකසීම්"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7771cf7..43bc29c 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
<string name="home_screen" msgid="5629429142036709174">"Domov"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastaviť aplikáciu <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ako predvolenú vstupnú aplikáciu v Nastaveniach"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdeliť obrazovku"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informácie o aplikácii pre %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavenia používania pre %1$s"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 2e75b17..5d423ca 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
<string name="home_screen" msgid="5629429142036709174">"Začetni zaslon"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastavitev zaganjalnika <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kot privzete aplikacije za začetni zaslon v nastavitvah"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Razdeljen zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Podatki o aplikaciji za: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavitve uporabe za »%1$s«"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index a00f11a..8f4133d 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
<string name="home_screen" msgid="5629429142036709174">"Ekrani bazë"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Cakto <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> si aplikacionin e parazgjedhur të ekranit bazë te \"Cilësimet\""</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekrani i ndarë"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacioni i aplikacionit për %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Cilësimet e përdorimit për \"%1$s\""</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 97d3ff4..cd6523c 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Виџети су онемогућени у Безбедном режиму"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Пречица није доступна"</string>
<string name="home_screen" msgid="5629429142036709174">"Почетни екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Подесите <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> као подразумевану почетну апликацију у Подешавањима"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Подељени екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Информације о апликацији за: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Подешавања потрошње за %1$s"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 8a8524e..4f95c67 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskärm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ställ in <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standardstartskärmsapp i Inställningar"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delad skärm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformation för %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Användningsinställningar för %1$s"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 64f6296..53a9abe 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
<string name="home_screen" msgid="5629429142036709174">"Skrini ya kwanza"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Weka <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> iwe programu chaguomsingi ya mwanzo kwenye Mipangilio"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gawa skrini"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Maelezo ya programu ya %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Mipangilio ya matumizi ya %1$s"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 189f4ce..738f85c 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ஷார்ட்கட் இல்லை"</string>
<string name="home_screen" msgid="5629429142036709174">"முகப்பு"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"அமைப்புகளில் <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> என்பதை இயல்பு முகப்பு ஆப்ஸாக அமையுங்கள்"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"திரைப் பிரிப்பு"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sக்கான ஆப்ஸ் தகவல்கள்"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sக்கான உபயோக அமைப்புகள்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index d789baa..0520ebf 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్లో విడ్జెట్లు నిలిపివేయబడ్డాయి"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"షార్ట్కట్ అందుబాటులో లేదు"</string>
<string name="home_screen" msgid="5629429142036709174">"మొదటి ట్యాబ్"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"సెట్టింగ్లలో <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ను ఆటోమేటిక్ సెట్టింగ్ మొదటి స్క్రీన్ యాప్గా సెట్ చేయండి"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"స్ప్లిట్ స్క్రీన్"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s కోసం యాప్ సమాచారం"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sకు సంబంధించిన వినియోగ సెట్టింగ్లు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index f89638d..554fd94 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"มีการปิดใช้งานวิดเจ็ตในเซฟโหมด"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ทางลัดไม่พร้อมใช้งาน"</string>
<string name="home_screen" msgid="5629429142036709174">"หน้าแรก"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ตั้ง <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> เป็นแอปหน้าแรกเริ่มต้นในการตั้งค่า"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"แยกหน้าจอ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ข้อมูลแอปสำหรับ %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"การตั้งค่าการใช้งานสำหรับ %1$s"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 8754a12..cb6fe66 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Itakda ang <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> bilang default na home app sa Mga Setting"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Impormasyon ng app para sa %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Mga setting ng paggamit para sa %1$s"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 761ce56..b12ec27 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
<string name="home_screen" msgid="5629429142036709174">"Ana ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> başlatıcısını Ayarlar\'da varsayılan ana ekran uygulaması olarak ayarlayın"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Bölünmüş ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s uygulama bilgileri"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ile ilgili kullanım ayarları"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 2c85d5a..2a01dae 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"У безпечному режимі віджети вимкнено"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлик недоступний"</string>
<string name="home_screen" msgid="5629429142036709174">"Головний екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Зробити <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> додатком головного екрана за умовчанням у налаштуваннях"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Розділити екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Інформація про додаток для %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Параметри використання (%1$s)"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 09f4304..544357e 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ویجیٹس کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"شارٹ کٹ دستیاب نہیں ہے"</string>
<string name="home_screen" msgid="5629429142036709174">"ہوم"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ترتیبات میں <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> کو بطور ڈیفالٹ ہوم ایپ سیٹ کریں"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"اسپلٹ اسکرین"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s کے لیے ایپ کی معلومات"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s کیلئے استعمال کی ترتیبات"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index d8dea69..5038d4f 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
<string name="home_screen" msgid="5629429142036709174">"Bosh ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Sozlamalar orqali <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ilovasini birlamchi bosh ekran ilovasi sifatida belgilash"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekranni ikkiga ajratish"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilovasi axboroti"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s uchun sarf sozlamalari"</string>
diff --git a/res/values-v30/styles.xml b/res/values-v30/styles.xml
index ec5c113..a5c57c8 100644
--- a/res/values-v30/styles.xml
+++ b/res/values-v30/styles.xml
@@ -29,5 +29,6 @@
<item name="android:windowLayoutInDisplayCutoutMode">always</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:enforceNavigationBarContrast">false</item>
+ <item name="materialColorOnPrimaryFixed">#FFFFFFFF</item>
</style>
</resources>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 7270366..5c81d49 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -110,39 +110,4 @@
@android:color/system_accent1_900</color>
<color name="overview_foreground_scrim_color">@android:color/system_neutral1_1000</color>
-
- <color name="material_color_on_secondary_fixed_variant">@android:color/system_accent2_700</color>
- <color name="material_color_on_tertiary_fixed_variant">@android:color/system_accent3_700</color>
- <color name="material_color_on_primary_fixed_variant">@android:color/system_accent1_700</color>
- <color name="material_color_on_secondary_container">@android:color/system_accent2_900</color>
- <color name="material_color_on_tertiary_container">@android:color/system_accent3_900</color>
- <color name="material_color_on_primary_container">@android:color/system_accent1_900</color>
- <color name="material_color_secondary_fixed_dim">@android:color/system_accent2_200</color>
- <color name="material_color_on_error_container">#410000</color>
- <color name="material_color_on_secondary_fixed">@android:color/system_accent2_900</color>
- <color name="material_color_on_surface_inverse">@android:color/system_neutral1_100</color>
- <color name="material_color_tertiary_fixed_dim">@android:color/system_accent3_200</color>
- <color name="material_color_on_tertiary_fixed">@android:color/system_accent3_900</color>
- <color name="material_color_primary_fixed_dim">@android:color/system_accent1_200</color>
- <color name="material_color_secondary_container">@android:color/system_accent2_100</color>
- <color name="material_color_error_container">#FFDAD5</color>
- <color name="material_color_on_primary_fixed">@android:color/system_accent1_900</color>
- <color name="material_color_primary_inverse">@android:color/system_accent1_200</color>
- <color name="material_color_secondary_fixed">@android:color/system_accent2_100</color>
- <color name="material_color_tertiary_container">@android:color/system_accent3_100</color>
- <color name="material_color_tertiary_fixed">@android:color/system_accent3_100</color>
- <color name="material_color_primary_container">@android:color/system_accent1_100</color>
- <color name="material_color_on_background">@android:color/system_neutral1_50</color>
- <color name="material_color_primary_fixed">@android:color/system_accent1_100</color>
- <color name="material_color_on_secondary">@android:color/system_accent2_0</color>
- <color name="material_color_on_tertiary">@android:color/system_accent3_0</color>
- <color name="material_color_on_error">#FFFFFF</color>
- <color name="material_color_on_surface_variant">@android:color/system_neutral2_700</color>
- <color name="material_color_outline">@android:color/system_neutral2_500</color>
- <color name="material_color_outline_variant">@android:color/system_neutral2_200</color>
- <color name="material_color_on_primary">@android:color/system_accent1_0</color>
- <color name="material_color_on_surface">@android:color/system_neutral1_900</color>
- <color name="material_color_primary">@android:color/system_accent1_600</color>
- <color name="material_color_secondary">@android:color/system_accent2_600</color>
- <color name="material_color_tertiary">@android:color/system_accent3_600</color>
</resources>
diff --git a/res/values-v34/colors.xml b/res/values-v34/colors.xml
index 26d3712..4f3a769 100644
--- a/res/values-v34/colors.xml
+++ b/res/values-v34/colors.xml
@@ -27,4 +27,108 @@
@android:color/system_on_surface_light</color>
<color name="widget_cell_subtitle_color_light">
@android:color/system_on_surface_variant_light</color>
+ <color name="widget_picker_menu_options_color_light">
+ @android:color/system_on_surface_variant_light
+ </color>
+
+ <color name="system_primary_container_light">@android:color/system_primary_container_light</color>
+ <color name="system_on_primary_container_light">@android:color/system_on_primary_container_light</color>
+ <color name="system_primary_light">@android:color/system_primary_light</color>
+ <color name="system_on_primary_light">@android:color/system_on_primary_light</color>
+ <color name="system_secondary_container_light">@android:color/system_secondary_container_light</color>
+ <color name="system_on_secondary_container_light">@android:color/system_on_secondary_container_light</color>
+ <color name="system_secondary_light">@android:color/system_secondary_light</color>
+ <color name="system_on_secondary_light">@android:color/system_on_secondary_light</color>
+ <color name="system_tertiary_container_light">@android:color/system_tertiary_container_light</color>
+ <color name="system_on_tertiary_container_light">@android:color/system_on_tertiary_container_light</color>
+ <color name="system_tertiary_light">@android:color/system_tertiary_light</color>
+ <color name="system_on_tertiary_light">@android:color/system_on_tertiary_light</color>
+ <color name="system_background_light">@android:color/system_background_light</color>
+ <color name="system_on_background_light">@android:color/system_on_background_light</color>
+ <color name="system_surface_light">@android:color/system_surface_light</color>
+ <color name="system_on_surface_light">@android:color/system_on_surface_light</color>
+ <color name="system_surface_container_low_light">@android:color/system_surface_container_low_light</color>
+ <color name="system_surface_container_lowest_light">@android:color/system_surface_container_lowest_light</color>
+ <color name="system_surface_container_light">@android:color/system_surface_container_light</color>
+ <color name="system_surface_container_high_light">@android:color/system_surface_container_high_light</color>
+ <color name="system_surface_container_highest_light">@android:color/system_surface_container_highest_light</color>
+ <color name="system_surface_bright_light">@android:color/system_surface_bright_light</color>
+ <color name="system_surface_dim_light">@android:color/system_surface_dim_light</color>
+ <color name="system_surface_variant_light">@android:color/system_surface_variant_light</color>
+ <color name="system_on_surface_variant_light">@android:color/system_on_surface_variant_light</color>
+ <color name="system_outline_light">@android:color/system_outline_light</color>
+ <color name="system_outline_variant_light">@android:color/system_outline_variant_light</color>
+ <color name="system_error_light">@android:color/system_error_light</color>
+ <color name="system_on_error_light">@android:color/system_on_error_light</color>
+ <color name="system_error_container_light">@android:color/system_error_container_light</color>
+ <color name="system_on_error_container_light">@android:color/system_on_error_container_light</color>
+ <color name="system_control_activated_light">@android:color/system_control_activated_light</color>
+ <color name="system_control_normal_light">@android:color/system_control_normal_light</color>
+ <color name="system_control_highlight_light">@android:color/system_control_highlight_light</color>
+ <color name="system_text_primary_inverse_light">@android:color/system_text_primary_inverse_light</color>
+ <color name="system_text_secondary_and_tertiary_inverse_light">@android:color/system_text_secondary_and_tertiary_inverse_light</color>
+ <color name="system_text_primary_inverse_disable_only_light">@android:color/system_text_primary_inverse_disable_only_light</color>
+ <color name="system_text_secondary_and_tertiary_inverse_disabled_light">@android:color/system_text_secondary_and_tertiary_inverse_disabled_light</color>
+ <color name="system_text_hint_inverse_light">@android:color/system_text_hint_inverse_light</color>
+ <color name="system_palette_key_color_primary_light">@android:color/system_palette_key_color_primary_light</color>
+ <color name="system_palette_key_color_secondary_light">@android:color/system_palette_key_color_secondary_light</color>
+ <color name="system_palette_key_color_tertiary_light">@android:color/system_palette_key_color_tertiary_light</color>
+ <color name="system_palette_key_color_neutral_light">@android:color/system_palette_key_color_neutral_light</color>
+ <color name="system_palette_key_color_neutral_variant_light">@android:color/system_palette_key_color_neutral_variant_light</color>
+ <color name="system_primary_container_dark">@android:color/system_primary_container_dark</color>
+ <color name="system_on_primary_container_dark">@android:color/system_on_primary_container_dark</color>
+ <color name="system_primary_dark">@android:color/system_primary_dark</color>
+ <color name="system_on_primary_dark">@android:color/system_on_primary_dark</color>
+ <color name="system_secondary_container_dark">@android:color/system_secondary_container_dark</color>
+ <color name="system_on_secondary_container_dark">@android:color/system_on_secondary_container_dark</color>
+ <color name="system_secondary_dark">@android:color/system_secondary_dark</color>
+ <color name="system_on_secondary_dark">@android:color/system_on_secondary_dark</color>
+ <color name="system_tertiary_container_dark">@android:color/system_tertiary_container_dark</color>
+ <color name="system_on_tertiary_container_dark">@android:color/system_on_tertiary_container_dark</color>
+ <color name="system_tertiary_dark">@android:color/system_tertiary_dark</color>
+ <color name="system_on_tertiary_dark">@android:color/system_on_tertiary_dark</color>
+ <color name="system_background_dark">@android:color/system_background_dark</color>
+ <color name="system_on_background_dark">@android:color/system_on_background_dark</color>
+ <color name="system_surface_dark">@android:color/system_surface_dark</color>
+ <color name="system_on_surface_dark">@android:color/system_on_surface_dark</color>
+ <color name="system_surface_container_low_dark">@android:color/system_surface_container_low_dark</color>
+ <color name="system_surface_container_lowest_dark">@android:color/system_surface_container_lowest_dark</color>
+ <color name="system_surface_container_dark">@android:color/system_surface_container_dark</color>
+ <color name="system_surface_container_high_dark">@android:color/system_surface_container_high_dark</color>
+ <color name="system_surface_container_highest_dark">@android:color/system_surface_container_highest_dark</color>
+ <color name="system_surface_bright_dark">@android:color/system_surface_bright_dark</color>
+ <color name="system_surface_dim_dark">@android:color/system_surface_dim_dark</color>
+ <color name="system_surface_variant_dark">@android:color/system_surface_variant_dark</color>
+ <color name="system_on_surface_variant_dark">@android:color/system_on_surface_variant_dark</color>
+ <color name="system_outline_dark">@android:color/system_outline_dark</color>
+ <color name="system_outline_variant_dark">@android:color/system_outline_variant_dark</color>
+ <color name="system_error_dark">@android:color/system_error_dark</color>
+ <color name="system_on_error_dark">@android:color/system_on_error_dark</color>
+ <color name="system_error_container_dark">@android:color/system_error_container_dark</color>
+ <color name="system_on_error_container_dark">@android:color/system_on_error_container_dark</color>
+ <color name="system_control_activated_dark">@android:color/system_control_activated_dark</color>
+ <color name="system_control_normal_dark">@android:color/system_control_normal_dark</color>
+ <color name="system_control_highlight_dark">@android:color/system_control_highlight_dark</color>
+ <color name="system_text_primary_inverse_dark">@android:color/system_text_primary_inverse_dark</color>
+ <color name="system_text_secondary_and_tertiary_inverse_dark">@android:color/system_text_secondary_and_tertiary_inverse_dark</color>
+ <color name="system_text_primary_inverse_disable_only_dark">@android:color/system_text_primary_inverse_disable_only_dark</color>
+ <color name="system_text_secondary_and_tertiary_inverse_disabled_dark">@android:color/system_text_secondary_and_tertiary_inverse_disabled_dark</color>
+ <color name="system_text_hint_inverse_dark">@android:color/system_text_hint_inverse_dark</color>
+ <color name="system_palette_key_color_primary_dark">@android:color/system_palette_key_color_primary_dark</color>
+ <color name="system_palette_key_color_secondary_dark">@android:color/system_palette_key_color_secondary_dark</color>
+ <color name="system_palette_key_color_tertiary_dark">@android:color/system_palette_key_color_tertiary_dark</color>
+ <color name="system_palette_key_color_neutral_dark">@android:color/system_palette_key_color_neutral_dark</color>
+ <color name="system_palette_key_color_neutral_variant_dark">@android:color/system_palette_key_color_neutral_variant_dark</color>
+ <color name="system_primary_fixed">@android:color/system_primary_fixed</color>
+ <color name="system_primary_fixed_dim">@android:color/system_primary_fixed_dim</color>
+ <color name="system_on_primary_fixed">@android:color/system_on_primary_fixed</color>
+ <color name="system_on_primary_fixed_variant">@android:color/system_on_primary_fixed_variant</color>
+ <color name="system_secondary_fixed">@android:color/system_secondary_fixed</color>
+ <color name="system_secondary_fixed_dim">@android:color/system_secondary_fixed_dim</color>
+ <color name="system_on_secondary_fixed">@android:color/system_on_secondary_fixed</color>
+ <color name="system_on_secondary_fixed_variant">@android:color/system_on_secondary_fixed_variant</color>
+ <color name="system_tertiary_fixed">@android:color/system_tertiary_fixed</color>
+ <color name="system_tertiary_fixed_dim">@android:color/system_tertiary_fixed_dim</color>
+ <color name="system_on_tertiary_fixed">@android:color/system_on_tertiary_fixed</color>
+ <color name="system_on_tertiary_fixed_variant">@android:color/system_on_tertiary_fixed_variant</color>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 172c995..e5252b1 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích bị vô hiệu hóa ở chế độ an toàn"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
<string name="home_screen" msgid="5629429142036709174">"Màn hình chính"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Đặt <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> làm trình chạy mặc định trong phần Cài đặt"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Chia đôi màn hình"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Thông tin ứng dụng cho %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Chế độ cài đặt mức sử dụng %1$s"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 9c658dc..7a76158 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用微件"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"无法使用快捷方式"</string>
<string name="home_screen" msgid="5629429142036709174">"主屏幕"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"在“设置”中将“<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>”设为默认主屏幕应用"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分屏"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的应用信息"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s的使用设置"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index af32638..ed6e52f 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式中無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"沒有可用的捷徑"</string>
<string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"前往「設定」將「<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>」設為預設主畫面應用程式"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割螢幕"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的應用程式資料"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 54a1c5d..264d607 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式下無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"目前無法使用捷徑"</string>
<string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"前往「設定」將「<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>」設為預設主畫面應用程式"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割畫面"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"「%1$s」的應用程式資訊"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 9752e18..be6cd49 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
<string name="home_screen" msgid="5629429142036709174">"Ikhaya"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Setha i-<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> njenge-app yasekhaya ezenzakalelayo Kumasethingi"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Hlukanisa isikrini"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Ulwazi lwe-App ye-%1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Amasethingi okusetshenziswa ka-%1$s"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e4e047e..6151b5f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -45,6 +45,55 @@
<attr name="focusOutlineColor" format="color" />
<attr name="focusInnerOutlineColor" format="color" />
+ <!-- Recreating Dynamic Color attributes found in the system. This should be the way to go on
+ all launcher projects since they all inherit from launcher3. Avoid creating other color
+ attributes if these can be user directly. -->
+ <attr name="materialColorOnSecondaryFixedVariant" format="color" />
+ <attr name="materialColorOnTertiaryFixedVariant" format="color" />
+ <attr name="materialColorSurfaceContainerLowest" format="color" />
+ <attr name="materialColorOnPrimaryFixedVariant" format="color" />
+ <attr name="materialColorOnSecondaryContainer" format="color" />
+ <attr name="materialColorOnTertiaryContainer" format="color" />
+ <attr name="materialColorSurfaceContainerLow" format="color" />
+ <attr name="materialColorOnPrimaryContainer" format="color" />
+ <attr name="materialColorSecondaryFixedDim" format="color" />
+ <attr name="materialColorOnErrorContainer" format="color" />
+ <attr name="materialColorOnSecondaryFixed" format="color" />
+ <attr name="materialColorOnSurfaceInverse" format="color" />
+ <attr name="materialColorTertiaryFixedDim" format="color" />
+ <attr name="materialColorOnTertiaryFixed" format="color" />
+ <attr name="materialColorPrimaryFixedDim" format="color" />
+ <attr name="materialColorSecondaryContainer" format="color" />
+ <attr name="materialColorErrorContainer" format="color" />
+ <attr name="materialColorOnPrimaryFixed" format="color" />
+ <attr name="materialColorPrimaryInverse" format="color" />
+ <attr name="materialColorSecondaryFixed" format="color" />
+ <attr name="materialColorSurfaceInverse" format="color" />
+ <attr name="materialColorSurfaceVariant" format="color" />
+ <attr name="materialColorTertiaryContainer" format="color" />
+ <attr name="materialColorTertiaryFixed" format="color" />
+ <attr name="materialColorPrimaryContainer" format="color" />
+ <attr name="materialColorOnBackground" format="color" />
+ <attr name="materialColorPrimaryFixed" format="color" />
+ <attr name="materialColorOnSecondary" format="color" />
+ <attr name="materialColorOnTertiary" format="color" />
+ <attr name="materialColorSurfaceDim" format="color" />
+ <attr name="materialColorSurfaceBright" format="color" />
+ <attr name="materialColorOnError" format="color" />
+ <attr name="materialColorSurface" format="color" />
+ <attr name="materialColorSurfaceContainerHigh" format="color" />
+ <attr name="materialColorSurfaceContainerHighest" format="color" />
+ <attr name="materialColorOnSurfaceVariant" format="color" />
+ <attr name="materialColorOutline" format="color" />
+ <attr name="materialColorOutlineVariant" format="color" />
+ <attr name="materialColorOnPrimary" format="color" />
+ <attr name="materialColorOnSurface" format="color" />
+ <attr name="materialColorSurfaceContainer" format="color" />
+ <attr name="materialColorPrimary" format="color" />
+ <attr name="materialColorSecondary" format="color" />
+ <attr name="materialColorTertiary" format="color" />
+ <attr name="materialColorError" format="color" />
+
<attr name="pageIndicatorDotColor" format="color" />
<attr name="folderPreviewColor" format="color" />
<attr name="folderBackgroundColor" format="color" />
@@ -62,6 +111,7 @@
<attr name="preloadIconBackgroundColor" format="color" />
<attr name="widgetPickerTitleColor" format="color"/>
<attr name="widgetPickerDescriptionColor" format="color"/>
+ <attr name="widgetPickerWidgetOptionsMenuColor" format="color"/>
<attr name="widgetPickerPrimarySurfaceColor" format="color"/>
<attr name="widgetPickerSecondarySurfaceColor" format="color"/>
<attr name="widgetPickerHeaderAppTitleColor" format="color"/>
@@ -581,51 +631,6 @@
<attr name="collapsable" format="boolean" />
</declare-styleable>
- <attr name="materialColorOnSecondaryFixedVariant" format="color" />
- <attr name="materialColorOnTertiaryFixedVariant" format="color" />
- <attr name="materialColorSurfaceContainerLowest" format="color" />
- <attr name="materialColorOnPrimaryFixedVariant" format="color" />
- <attr name="materialColorOnSecondaryContainer" format="color" />
- <attr name="materialColorOnTertiaryContainer" format="color" />
- <attr name="materialColorSurfaceContainerLow" format="color" />
- <attr name="materialColorOnPrimaryContainer" format="color" />
- <attr name="materialColorSecondaryFixedDim" format="color" />
- <attr name="materialColorOnErrorContainer" format="color" />
- <attr name="materialColorOnSecondaryFixed" format="color" />
- <attr name="materialColorOnSurfaceInverse" format="color" />
- <attr name="materialColorTertiaryFixedDim" format="color" />
- <attr name="materialColorOnTertiaryFixed" format="color" />
- <attr name="materialColorPrimaryFixedDim" format="color" />
- <attr name="materialColorSecondaryContainer" format="color" />
- <attr name="materialColorErrorContainer" format="color" />
- <attr name="materialColorOnPrimaryFixed" format="color" />
- <attr name="materialColorPrimaryInverse" format="color" />
- <attr name="materialColorSecondaryFixed" format="color" />
- <attr name="materialColorTertiaryContainer" format="color" />
- <attr name="materialColorTertiaryFixed" format="color" />
- <attr name="materialColorPrimaryContainer" format="color" />
- <attr name="materialColorOnBackground" format="color" />
- <attr name="materialColorPrimaryFixed" format="color" />
- <attr name="materialColorOnSecondary" format="color" />
- <attr name="materialColorOnTertiary" format="color" />
- <attr name="materialColorOnError" format="color" />
- <attr name="materialColorOnSurfaceVariant" format="color" />
- <attr name="materialColorOutline" format="color" />
- <attr name="materialColorOutlineVariant" format="color" />
- <attr name="materialColorOnPrimary" format="color" />
- <attr name="materialColorOnSurface" format="color" />
- <attr name="materialColorPrimary" format="color" />
- <attr name="materialColorSecondary" format="color" />
- <attr name="materialColorTertiary" format="color" />
- <attr name="materialColorSurfaceInverse" format="color" />
- <attr name="materialColorSurfaceVariant" format="color" />
- <attr name="materialColorSurfaceDim" format="color" />
- <attr name="materialColorSurfaceBright" format="color" />
- <attr name="materialColorSurface" format="color" />
- <attr name="materialColorSurfaceContainerHigh" format="color" />
- <attr name="materialColorSurfaceContainerHighest" format="color" />
- <attr name="materialColorSurfaceContainer" format="color" />
-
<declare-styleable name="WidgetSections">
<!-- Component name of an app widget provider. -->
<attr name="provider" format="string" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 8fa1992..c82358b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -17,8 +17,7 @@
** limitations under the License.
*/
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The color tints to apply to the text and drag view when hovering
over the delete target or the info target -->
<color name="delete_target_hover_tint">#FFC1C1C1</color>
@@ -105,6 +104,7 @@
<color name="widget_picker_secondary_surface_color_light">#FAF9F8</color>
<color name="widget_picker_title_color_light">#1F1F1F</color>
<color name="widget_picker_description_color_light">#4C4D50</color>
+ <color name="widget_picker_menu_options_color_light">@color/system_on_surface_variant_light</color>
<color name="widget_picker_header_app_title_color_light">#1F1F1F</color>
<color name="widget_picker_header_app_subtitle_color_light">#444746</color>
<color name="widget_picker_header_background_color_light">#C2E7FF</color>
@@ -118,13 +118,14 @@
<color name="widget_picker_collapse_handle_color_light">#C4C7C5</color>
<color name="widget_picker_add_button_background_color_light">#0B57D0</color>
<color name="widget_picker_add_button_text_color_light">#0B57D0</color>
- <color name="widget_cell_title_color_light">@color/material_color_on_surface</color>
- <color name="widget_cell_subtitle_color_light">@color/material_color_on_surface_variant</color>
+ <color name="widget_cell_title_color_light">@color/system_on_surface_light</color>
+ <color name="widget_cell_subtitle_color_light">@color/system_on_surface_variant_light</color>
<color name="widget_picker_primary_surface_color_dark">#1F2020</color>
<color name="widget_picker_secondary_surface_color_dark">#393939</color>
<color name="widget_picker_title_color_dark">#E3E3E3</color>
<color name="widget_picker_description_color_dark">#CCCDCF</color>
+ <color name="widget_picker_menu_options_color_dark">@color/system_on_surface_variant_dark</color>
<color name="widget_picker_header_app_title_color_dark">#E3E3E3</color>
<color name="widget_picker_header_app_subtitle_color_dark">#C4C7C5</color>
<color name="widget_picker_header_background_color_dark">#004A77</color>
@@ -138,51 +139,107 @@
<color name="widget_picker_collapse_handle_color_dark">#444746</color>
<color name="widget_picker_add_button_background_color_dark">#062E6F</color>
<color name="widget_picker_add_button_text_color_dark">#FFFFFF</color>
- <color name="widget_cell_title_color_dark">@color/material_color_on_surface</color>
- <color name="widget_cell_subtitle_color_dark">@color/material_color_on_surface_variant</color>
+ <color name="widget_cell_title_color_dark">@color/system_on_surface_dark</color>
+ <color name="widget_cell_subtitle_color_dark">@color/system_on_surface_variant_dark</color>
- <color name="material_color_on_secondary_fixed_variant">#3F4759</color>
- <color name="material_color_on_tertiary_fixed_variant">#583E5B</color>
- <color name="material_color_surface_container_lowest">#FFFFFF</color>
- <color name="material_color_on_primary_fixed_variant">#2B4678</color>
- <color name="material_color_on_secondary_container">#141B2C</color>
- <color name="material_color_on_tertiary_container">#29132D</color>
- <color name="material_color_surface_container_low">#F5F3F7</color>
- <color name="material_color_on_primary_container">#001A41</color>
- <color name="material_color_secondary_fixed_dim">#BFC6DC</color>
- <color name="material_color_on_error_container">#410000</color>
- <color name="material_color_on_secondary_fixed">#141B2C</color>
- <color name="material_color_on_surface_inverse">#E3E2E6</color>
- <color name="material_color_tertiary_fixed_dim">#DEBCDF</color>
- <color name="material_color_on_tertiary_fixed">#29132D</color>
- <color name="material_color_primary_fixed_dim">#ADC6FF</color>
- <color name="material_color_secondary_container">#DBE2F9</color>
- <color name="material_color_error_container">#FFDAD5</color>
- <color name="material_color_on_primary_fixed">#001A41</color>
- <color name="material_color_primary_inverse">#ADC6FF</color>
- <color name="material_color_secondary_fixed">#DBE2F9</color>
- <color name="material_color_surface_inverse">#121316</color>
- <color name="material_color_surface_variant">#E1E2EC</color>
- <color name="material_color_tertiary_container">#FBD7FC</color>
- <color name="material_color_tertiary_fixed">#FBD7FC</color>
- <color name="material_color_primary_container">#D8E2FF</color>
- <color name="material_color_on_background">#1B1B1F</color>
- <color name="material_color_primary_fixed">#D8E2FF</color>
- <color name="material_color_on_secondary">#FFFFFF</color>
- <color name="material_color_on_tertiary">#FFFFFF</color>
- <color name="material_color_surface_dim">#DBD9DD</color>
- <color name="material_color_surface_bright">#FAF9FD</color>
- <color name="material_color_on_error">#FFFFFF</color>
- <color name="material_color_surface">#FAF9FD</color>
- <color name="material_color_surface_container_high">#E9E7EC</color>
- <color name="material_color_surface_container_highest">#E3E2E6</color>
- <color name="material_color_on_surface_variant">#44474F</color>
- <color name="material_color_outline">#72747D</color>
- <color name="material_color_outline_variant">#C4C7C5</color>
- <color name="material_color_on_primary">#FFFFFF</color>
- <color name="material_color_on_surface">#1B1B1F</color>
- <color name="material_color_surface_container">#EFEDF1</color>
- <color name="material_color_primary">#445E91</color>
- <color name="material_color_secondary">#575E71</color>
- <color name="material_color_tertiary">#715573</color>
+ <color name="system_primary_container_light">#D9E2FF</color>
+ <color name="system_on_primary_container_light">#001945</color>
+ <color name="system_primary_light">#475D92</color>
+ <color name="system_on_primary_light">#FFFFFF</color>
+ <color name="system_secondary_container_light">#DCE2F9</color>
+ <color name="system_on_secondary_container_light">#151B2C</color>
+ <color name="system_secondary_light">#575E71</color>
+ <color name="system_on_secondary_light">#FFFFFF</color>
+ <color name="system_tertiary_container_light">#FDD7FA</color>
+ <color name="system_on_tertiary_container_light">#2A122C</color>
+ <color name="system_tertiary_light">#725572</color>
+ <color name="system_on_tertiary_light">#FFFFFF</color>
+ <color name="system_background_light">#FAF8FF</color>
+ <color name="system_on_background_light">#1A1B20</color>
+ <color name="system_surface_light">#FAF8FF</color>
+ <color name="system_on_surface_light">#1A1B20</color>
+ <color name="system_surface_container_low_light">#F4F3FA</color>
+ <color name="system_surface_container_lowest_light">#FFFFFF</color>
+ <color name="system_surface_container_light">#EEEDF4</color>
+ <color name="system_surface_container_high_light">#E8E7EF</color>
+ <color name="system_surface_container_highest_light">#E2E2E9</color>
+ <color name="system_surface_bright_light">#FAF8FF</color>
+ <color name="system_surface_dim_light">#DAD9E0</color>
+ <color name="system_surface_variant_light">#E1E2EC</color>
+ <color name="system_on_surface_variant_light">#44464F</color>
+ <color name="system_outline_light">#757780</color>
+ <color name="system_outline_variant_light">#C5C6D0</color>
+ <color name="system_error_light">#BA1A1A</color>
+ <color name="system_on_error_light">#FFFFFF</color>
+ <color name="system_error_container_light">#FFDAD6</color>
+ <color name="system_on_error_container_light">#410002</color>
+ <color name="system_control_activated_light">#D9E2FF</color>
+ <color name="system_control_normal_light">#44464F</color>
+ <color name="system_control_highlight_light">#000000</color>
+ <color name="system_text_primary_inverse_light">#E2E2E9</color>
+ <color name="system_text_secondary_and_tertiary_inverse_light">#C5C6D0</color>
+ <color name="system_text_primary_inverse_disable_only_light">#E2E2E9</color>
+ <color name="system_text_secondary_and_tertiary_inverse_disabled_light">#E2E2E9</color>
+ <color name="system_text_hint_inverse_light">#E2E2E9</color>
+ <color name="system_palette_key_color_primary_light">#6076AC</color>
+ <color name="system_palette_key_color_secondary_light">#70778B</color>
+ <color name="system_palette_key_color_tertiary_light">#8C6D8C</color>
+ <color name="system_palette_key_color_neutral_light">#76777D</color>
+ <color name="system_palette_key_color_neutral_variant_light">#757780</color>
+ <color name="system_primary_container_dark">#2F4578</color>
+ <color name="system_on_primary_container_dark">#D9E2FF</color>
+ <color name="system_primary_dark">#B0C6FF</color>
+ <color name="system_on_primary_dark">#152E60</color>
+ <color name="system_secondary_container_dark">#404659</color>
+ <color name="system_on_secondary_container_dark">#DCE2F9</color>
+ <color name="system_secondary_dark">#C0C6DC</color>
+ <color name="system_on_secondary_dark">#2A3042</color>
+ <color name="system_tertiary_container_dark">#593D59</color>
+ <color name="system_on_tertiary_container_dark">#FDD7FA</color>
+ <color name="system_tertiary_dark">#E0BBDD</color>
+ <color name="system_on_tertiary_dark">#412742</color>
+ <color name="system_background_dark">#121318</color>
+ <color name="system_on_background_dark">#E2E2E9</color>
+ <color name="system_surface_dark">#121318</color>
+ <color name="system_on_surface_dark">#E2E2E9</color>
+ <color name="system_surface_container_low_dark">#1A1B20</color>
+ <color name="system_surface_container_lowest_dark">#0C0E13</color>
+ <color name="system_surface_container_dark">#1E1F25</color>
+ <color name="system_surface_container_high_dark">#282A2F</color>
+ <color name="system_surface_container_highest_dark">#33343A</color>
+ <color name="system_surface_bright_dark">#38393F</color>
+ <color name="system_surface_dim_dark">#121318</color>
+ <color name="system_surface_variant_dark">#44464F</color>
+ <color name="system_on_surface_variant_dark">#C5C6D0</color>
+ <color name="system_outline_dark">#8F9099</color>
+ <color name="system_outline_variant_dark">#44464F</color>
+ <color name="system_error_dark">#FFB4AB</color>
+ <color name="system_on_error_dark">#690005</color>
+ <color name="system_error_container_dark">#93000A</color>
+ <color name="system_on_error_container_dark">#FFDAD6</color>
+ <color name="system_control_activated_dark">#2F4578</color>
+ <color name="system_control_normal_dark">#C5C6D0</color>
+ <color name="system_control_highlight_dark">#FFFFFF</color>
+ <color name="system_text_primary_inverse_dark">#1A1B20</color>
+ <color name="system_text_secondary_and_tertiary_inverse_dark">#44464F</color>
+ <color name="system_text_primary_inverse_disable_only_dark">#1A1B20</color>
+ <color name="system_text_secondary_and_tertiary_inverse_disabled_dark">#1A1B20</color>
+ <color name="system_text_hint_inverse_dark">#1A1B20</color>
+ <color name="system_palette_key_color_primary_dark">#6076AC</color>
+ <color name="system_palette_key_color_secondary_dark">#70778B</color>
+ <color name="system_palette_key_color_tertiary_dark">#8C6D8C</color>
+ <color name="system_palette_key_color_neutral_dark">#76777D</color>
+ <color name="system_palette_key_color_neutral_variant_dark">#757780</color>
+ <color name="system_primary_fixed">#D9E2FF</color>
+ <color name="system_primary_fixed_dim">#B0C6FF</color>
+ <color name="system_on_primary_fixed">#001945</color>
+ <color name="system_on_primary_fixed_variant">#2F4578</color>
+ <color name="system_secondary_fixed">#DCE2F9</color>
+ <color name="system_secondary_fixed_dim">#C0C6DC</color>
+ <color name="system_on_secondary_fixed">#151B2C</color>
+ <color name="system_on_secondary_fixed_variant">#404659</color>
+ <color name="system_tertiary_fixed">#FDD7FA</color>
+ <color name="system_tertiary_fixed_dim">#E0BBDD</color>
+ <color name="system_on_tertiary_fixed">#2A122C</color>
+ <color name="system_on_tertiary_fixed_variant">#593D59</color>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d33adc4..3b458c2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -61,6 +61,12 @@
<string name="long_press_widget_to_add">Touch & hold to move a widget.</string>
<!-- Accessibility spoken hint message in widget picker, which allows user to add a widget. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
<string name="long_accessible_way_to_add">Double-tap & hold to move a widget or use custom actions.</string>
+ <!-- Accessibility label for the icon button shown in the widget picker that opens a overflow
+ menu with widgets viewing options. [CHAR_LIMIT=25] -->
+ <string name="widget_picker_widget_options_button_description">More options</string>
+ <!-- Label for the checkbox shown in the widget picker toggles whether to show all widgets or
+ the default set. [CHAR_LIMIT=25] -->
+ <string name="widget_picker_show_all_widgets_menu_item_title">Show all widgets</string>
<!-- The format string for the dimensions of a widget in the drawer -->
<!-- There is a special version of this format string for Farsi -->
<string name="widget_dims_format">%1$d \u00d7 %2$d</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f7273a0..ee7ed26 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -17,7 +17,7 @@
*/
-->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Launcher theme -->
<style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.Light">
<item name="disabledIconAlpha">.54</item>
@@ -29,17 +29,65 @@
<item name="android:windowShowWallpaper">true</item>
</style>
- <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
+ <style name="DynamicColorsBaseLauncherTheme" parent="@style/BaseLauncherTheme">
+ <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+ <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
+ <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
+ <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
+ <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+ <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+ <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+ <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
+ <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+ <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
+ <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
+ <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
+ <item name="materialColorPrimary">@color/system_primary_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorError">@color/system_error_light</item>
+ </style>
+
+
+ <style name="LauncherTheme" parent="@style/DynamicColorsBaseLauncherTheme">
<item name="android:textColorSecondary">#DE000000</item>
<item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
- <item name="allappsHeaderProtectionColor">
- @color/material_color_surface_container_highest</item>
+ <item name="allappsHeaderProtectionColor">?attr/materialColorSurfaceContainerHighest</item>
<item name="allAppsNavBarScrimColor">#66FFFFFF</item>
<item name="popupColorPrimary">@color/popup_color_primary_light</item>
<item name="popupColorSecondary">@color/popup_color_secondary_light</item>
<item name="popupColorTertiary">@color/popup_color_tertiary_light</item>
<item name="popupColorBackground">#EFEDED</item>
- <item name="popupTextColor">@color/popup_text_color_light</item>
+ <item name="popupTextColor">@color/system_on_surface_light</item>
<item name="popupShadeFirst">@color/popup_shade_first_light</item>
<item name="popupShadeSecond">@color/popup_shade_second_light</item>
<item name="popupShadeThird">@color/popup_shade_third_light</item>
@@ -53,15 +101,15 @@
<item name="workspaceKeyShadowColor">#89000000</item>
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
- <item name="focusOutlineColor">@color/material_color_secondary_fixed</item>
- <item name="focusInnerOutlineColor">@color/material_color_on_secondary_fixed_variant</item>
+ <item name="focusOutlineColor">?attr/materialColorSecondaryFixed</item>
+ <item name="focusInnerOutlineColor">?attr/materialColorOnSecondaryFixedVariant</item>
<item name="folderPreviewColor">@color/folder_preview_light</item>
<item name="folderBackgroundColor">@color/folder_background_light</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="isFolderDarkText">true</item>
<item name="folderTextColor">@color/folder_text_color_light</item>
<item name="folderHintTextColor">@color/folder_hint_text_color_light</item>
- <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
+ <item name="appPairSurfaceInFolder">?attr/materialColorSurfaceContainerLowest</item>
<item name="loadingIconColor">#CCFFFFFF</item>
<item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
<item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
@@ -78,51 +126,6 @@
<item name="android:statusBarColor">#00000000</item>
<item name="android:navigationBarColor">#00000000</item>
<item name="android:switchStyle">@style/SwitchStyle</item>
-
- <item name="materialColorOnSecondaryFixedVariant">@color/material_color_on_secondary_fixed_variant</item>
- <item name="materialColorOnTertiaryFixedVariant">@color/material_color_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/material_color_surface_container_lowest</item>
- <item name="materialColorOnPrimaryFixedVariant">@color/material_color_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/material_color_on_secondary_container</item>
- <item name="materialColorOnTertiaryContainer">@color/material_color_on_tertiary_container</item>
- <item name="materialColorSurfaceContainerLow">@color/material_color_surface_container_low</item>
- <item name="materialColorOnPrimaryContainer">@color/material_color_on_primary_container</item>
- <item name="materialColorSecondaryFixedDim">@color/material_color_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/material_color_on_error_container</item>
- <item name="materialColorOnSecondaryFixed">@color/material_color_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/material_color_on_surface_inverse</item>
- <item name="materialColorTertiaryFixedDim">@color/material_color_tertiary_fixed_dim</item>
- <item name="materialColorOnTertiaryFixed">@color/material_color_on_tertiary_fixed</item>
- <item name="materialColorPrimaryFixedDim">@color/material_color_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/material_color_secondary_container</item>
- <item name="materialColorErrorContainer">@color/material_color_error_container</item>
- <item name="materialColorOnPrimaryFixed">@color/material_color_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/material_color_primary_inverse</item>
- <item name="materialColorSecondaryFixed">@color/material_color_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/material_color_surface_inverse</item>
- <item name="materialColorSurfaceVariant">@color/material_color_surface_variant</item>
- <item name="materialColorTertiaryContainer">@color/material_color_tertiary_container</item>
- <item name="materialColorTertiaryFixed">@color/material_color_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/material_color_primary_container</item>
- <item name="materialColorOnBackground">@color/material_color_on_background</item>
- <item name="materialColorPrimaryFixed">@color/material_color_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/material_color_on_secondary</item>
- <item name="materialColorOnTertiary">@color/material_color_on_tertiary</item>
- <item name="materialColorSurfaceDim">@color/material_color_surface_dim</item>
- <item name="materialColorSurfaceBright">@color/material_color_surface_bright</item>
- <item name="materialColorOnError">@color/material_color_on_error</item>
- <item name="materialColorSurface">@color/material_color_surface</item>
- <item name="materialColorSurfaceContainerHigh">@color/material_color_surface_container_high</item>
- <item name="materialColorSurfaceContainerHighest">@color/material_color_surface_container_highest</item>
- <item name="materialColorOnSurfaceVariant">@color/material_color_on_surface_variant</item>
- <item name="materialColorOutline">@color/material_color_outline</item>
- <item name="materialColorOutlineVariant">@color/material_color_outline_variant</item>
- <item name="materialColorOnPrimary">@color/material_color_on_primary</item>
- <item name="materialColorOnSurface">@color/material_color_on_surface</item>
- <item name="materialColorSurfaceContainer">@color/material_color_surface_container</item>
- <item name="materialColorPrimary">@color/material_color_primary</item>
- <item name="materialColorSecondary">@color/material_color_secondary</item>
- <item name="materialColorTertiary">@color/material_color_tertiary</item>
</style>
<style name="SwitchStyle"
@@ -153,13 +156,13 @@
<item name="android:colorControlHighlight">#19FFFFFF</item>
<item name="android:colorPrimary">#FF212121</item>
<item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
- <item name="allappsHeaderProtectionColor">@color/material_color_surface_container_low</item>
+ <item name="allappsHeaderProtectionColor">?attr/materialColorSurfaceContainerLow</item>
<item name="allAppsNavBarScrimColor">#80000000</item>
<item name="popupColorPrimary">@color/popup_color_primary_dark</item>
<item name="popupColorSecondary">@color/popup_color_secondary_dark</item>
<item name="popupColorTertiary">@color/popup_color_tertiary_dark</item>
<item name="popupColorBackground">#1F2020</item>
- <item name="popupTextColor">@color/popup_text_color_dark</item>
+ <item name="popupTextColor">@color/system_on_surface_dark</item>
<item name="popupNotificationDotColor">@color/popup_notification_dot_dark</item>
<item name="popupShadeFirst">@color/popup_shade_first_dark</item>
<item name="popupShadeSecond">@color/popup_shade_second_dark</item>
@@ -173,7 +176,7 @@
<item name="isFolderDarkText">false</item>
<item name="folderTextColor">@color/folder_text_color_dark</item>
<item name="folderHintTextColor">@color/folder_hint_text_color_dark</item>
- <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
+ <item name="appPairSurfaceInFolder">?attr/materialColorSurfaceContainerLowest</item>
<item name="isMainColorDark">true</item>
<item name="loadingIconColor">#99FFFFFF</item>
<item name="iconOnlyShortcutColor">#B3FFFFFF</item>
@@ -244,6 +247,7 @@
@color/widget_picker_secondary_surface_color_light</item>
<item name="widgetPickerTitleColor">@color/widget_picker_title_color_light</item>
<item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_light</item>
+ <item name="widgetPickerWidgetOptionsMenuColor">@color/widget_picker_menu_options_color_light</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_light</item>
<item name="widgetPickerHeaderAppSubtitleColor">
@@ -284,6 +288,7 @@
<item name="widgetPickerTitleColor">
@color/widget_picker_title_color_dark</item>
<item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_dark</item>
+ <item name="widgetPickerWidgetOptionsMenuColor">@color/widget_picker_menu_options_color_dark</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_dark</item>
<item name="widgetPickerHeaderAppSubtitleColor">
@@ -463,7 +468,7 @@
<style name="PrivateSpaceHeaderTextStyle">
<item name="android:textSize">16sp</item>
- <item name="android:textColor">@color/material_color_on_surface</item>
+ <item name="android:textColor">?attr/materialColorOnSurface</item>
<item name="android:fontFamily">google-sans-text-medium</item>
<item name="android:ellipsize">end</item>
</style>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 83427a0..0cb2137 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -40,11 +40,15 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
+import android.text.style.ImageSpan;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Property;
import android.util.Size;
import android.util.TypedValue;
@@ -54,6 +58,7 @@
import android.view.ViewDebug;
import android.widget.TextView;
+import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
@@ -96,6 +101,8 @@
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
IconLabelDotView, DraggableView, Reorderable {
+ public static final String TAG = "BubbleTextView";
+
public static final int DISPLAY_WORKSPACE = 0;
public static final int DISPLAY_ALL_APPS = 1;
public static final int DISPLAY_FOLDER = 2;
@@ -494,7 +501,13 @@
mLastOriginalText = label;
mLastModifiedText = mLastOriginalText;
mBreakPointsIntArray = StringMatcherUtility.getListOfBreakpoints(label, MATCHER);
- setText(label);
+ if (Flags.enableNewArchivingIcon()
+ && info instanceof ItemInfoWithIcon infoWithIcon
+ && infoWithIcon.isInactiveArchive()) {
+ setTextWithStartIcon(label, R.drawable.cloud_download_24px);
+ } else {
+ setText(label);
+ }
}
if (info.contentDescription != null) {
setContentDescription(info.isDisabled()
@@ -804,7 +817,13 @@
getLineSpacingExtra());
if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
mLastModifiedText = modifiedString;
- setText(modifiedString);
+ if (Flags.enableNewArchivingIcon()
+ && getTag() instanceof ItemInfoWithIcon infoWithIcon
+ && infoWithIcon.isInactiveArchive()) {
+ setTextWithStartIcon(modifiedString, R.drawable.cloud_download_24px);
+ } else {
+ setText(modifiedString);
+ }
// if text contains NEW_LINE, set max lines to 2
if (TextUtils.indexOf(modifiedString, NEW_LINE) != -1) {
setSingleLine(false);
@@ -825,6 +844,28 @@
super.setTextColor(getModifiedColor());
}
+ /**
+ * Uses a SpannableString to set text with a Drawable at the start of the TextView
+ * @param text text to use for TextView
+ * @param drawableRes Drawable Resource to use for drawing image at start of text
+ */
+ private void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableRes) {
+ Drawable drawable = getContext().getDrawable(drawableRes);
+ if (drawable == null) {
+ setText(text);
+ Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources"
+ + ", will just set text instead. text=" + text);
+ return;
+ }
+ drawable.setTint(getCurrentTextColor());
+ drawable.setBounds(0, 0, Math.round(getTextSize()), Math.round(getTextSize()));
+ ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_CENTER);
+ // First space will be replaced with Drawable, second space is for space before text.
+ SpannableString spannable = new SpannableString(" " + text);
+ spannable.setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ setText(spannable);
+ }
+
@Override
public void setTextColor(ColorStateList colors) {
mTextColor = colors.getDefaultColor();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index cb897dc..5949732 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -70,6 +70,7 @@
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW;
import static com.android.launcher3.logging.StatsLogManager.EventEnum;
@@ -188,7 +189,6 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.LauncherDragController;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -816,7 +816,7 @@
View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container);
if (collectionIcon instanceof FolderIcon folderIcon
&& collectionIcon.getTag() instanceof FolderInfo) {
- if (new FolderGridOrganizer(getDeviceProfile())
+ if (createFolderGridOrganizer(getDeviceProfile())
.setFolderInfo((FolderInfo) folderIcon.getTag())
.isItemInPreview(info.rank)) {
folderIcon.invalidate();
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 85c8b57..b41da0f 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -115,6 +115,9 @@
if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
params.setEnableUnarchivalConfirmation(false);
+ if (Flags.enableNewArchivingIcon()) {
+ params.setEnableIconOverlay(false);
+ }
launcherApps.setArchiveCompatibility(params);
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index a296f46..a448228 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -106,8 +106,6 @@
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Various utilities shared amongst the Launcher's classes.
@@ -116,8 +114,7 @@
private static final String TAG = "Launcher.Utilities";
- private static final Pattern sTrimPattern =
- Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
+ private static final String TRIM_PATTERN = "(^\\h+|\\h+$)";
private static final Matrix sMatrix = new Matrix();
private static final Matrix sInverseMatrix = new Matrix();
@@ -445,10 +442,7 @@
if (s == null) {
return "";
}
-
- // Just strip any sequence of whitespace or java space characters from the beginning and end
- Matcher m = sTrimPattern.matcher(s);
- return m.replaceAll("$1");
+ return s.toString().replaceAll(TRIM_PATTERN, "").trim();
}
/**
@@ -722,14 +716,59 @@
}
/**
- * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
+ * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CW. Parent
* sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
* the final bounds.
+ *
+ * As an example if this is the input:
+ * +-------------+
+ * | +-----+ |
+ * | | | |
+ * | +-----+ |
+ * | |
+ * | |
+ * | |
+ * +-------------+
+ * This would be case delta % 4 == 0:
+ * +-------------+
+ * | +-----+ |
+ * | | | |
+ * | +-----+ |
+ * | |
+ * | |
+ * | |
+ * +-------------+
+ * This would be case delta % 4 == 1:
+ * +----------------+
+ * | +--+ |
+ * | | | |
+ * | | | |
+ * | +--+ |
+ * | |
+ * +----------------+
+ * This would be case delta % 4 == 2:
+ * +-------------+
+ * | |
+ * | |
+ * | |
+ * | +-----+ |
+ * | | | |
+ * | +-----+ |
+ * +-------------+
+ * This would be case delta % 4 == 3:
+ * +----------------+
+ * | +--+ |
+ * | | | |
+ * | | | |
+ * | +--+ |
+ * | |
+ * +----------------+
*/
public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
int delta) {
int rdelta = ((delta % 4) + 4) % 4;
int origLeft = inOutBounds.left;
+ int origTop = inOutBounds.top;
switch (rdelta) {
case 0:
return;
@@ -741,6 +780,8 @@
return;
case 2:
inOutBounds.left = parentWidth - inOutBounds.right;
+ inOutBounds.top = parentHeight - inOutBounds.bottom;
+ inOutBounds.bottom = parentHeight - origTop;
inOutBounds.right = parentWidth - origLeft;
return;
case 3:
@@ -830,6 +871,9 @@
@NonNull Rect inclusionBounds,
@NonNull Rect exclusionBounds,
@AdjustmentDirection int adjustmentDirection) {
+ if (!Rect.intersects(targetViewBounds, exclusionBounds)) {
+ return;
+ }
switch (adjustmentDirection) {
case TRANSLATE_RIGHT:
targetView.setTranslationX(Math.min(
diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
deleted file mode 100644
index 79b8187..0000000
--- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.accessibility;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.OnHierarchyChangeListener;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.dragndrop.DragController.DragListener;
-import com.android.launcher3.dragndrop.DragOptions;
-
-import java.util.function.Function;
-
-/**
- * Utility listener to enable/disable accessibility drag flags for a ViewGroup
- * containing CellLayouts
- */
-public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener {
-
- private final ViewGroup mViewGroup;
- private final Function<CellLayout, DragAndDropAccessibilityDelegate> mDelegateFactory;
-
- /**
- * @param parent the viewgroup containing all the children
- * @param delegateFactory function to create no delegates
- */
- public AccessibleDragListenerAdapter(ViewGroup parent,
- Function<CellLayout, DragAndDropAccessibilityDelegate> delegateFactory) {
- mViewGroup = parent;
- mDelegateFactory = delegateFactory;
- }
-
- @Override
- public void onDragStart(DragObject dragObject, DragOptions options) {
- mViewGroup.setOnHierarchyChangeListener(this);
- enableAccessibleDrag(true, dragObject);
- }
-
- @Override
- public void onDragEnd() {
- mViewGroup.setOnHierarchyChangeListener(null);
- enableAccessibleDrag(false, null);
- Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this);
- }
-
-
- @Override
- public void onChildViewAdded(View parent, View child) {
- if (parent == mViewGroup) {
- setEnableForLayout((CellLayout) child, true);
- }
- }
-
- @Override
- public void onChildViewRemoved(View parent, View child) {
- if (parent == mViewGroup) {
- setEnableForLayout((CellLayout) child, false);
- }
- }
-
- protected void enableAccessibleDrag(boolean enable, @Nullable DragObject dragObject) {
- for (int i = 0; i < mViewGroup.getChildCount(); i++) {
- setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable);
- }
- }
-
- protected final void setEnableForLayout(CellLayout layout, boolean enable) {
- layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null);
- }
-}
diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt
new file mode 100644
index 0000000..21c2caf
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.accessibility
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.launcher3.CellLayout
+import com.android.launcher3.DropTarget.DragObject
+import com.android.launcher3.dragndrop.DragController
+import com.android.launcher3.dragndrop.DragOptions
+import com.android.launcher3.views.ActivityContext
+import java.util.function.Function
+
+/**
+ * Utility listener to enable/disable accessibility drag flags for a ViewGroup containing
+ * CellLayouts
+ */
+open class AccessibleDragListenerAdapter
+/**
+ * @param parent the viewgroup containing all the children
+ * @param delegateFactory function to create no delegates
+ */
+(
+ private val mViewGroup: ViewGroup,
+ private val mDelegateFactory: Function<CellLayout, DragAndDropAccessibilityDelegate>
+) : DragController.DragListener, ViewGroup.OnHierarchyChangeListener {
+ override fun onDragStart(dragObject: DragObject, options: DragOptions) {
+ mViewGroup.setOnHierarchyChangeListener(this)
+ enableAccessibleDrag(true, dragObject)
+ }
+
+ override fun onDragEnd() {
+ mViewGroup.setOnHierarchyChangeListener(null)
+ enableAccessibleDrag(false, null)
+ val activityContext = ActivityContext.lookupContext(mViewGroup.context) as ActivityContext
+ activityContext.getDragController<DragController<*>>()?.removeDragListener(this)
+ }
+
+ override fun onChildViewAdded(parent: View, child: View) {
+ if (parent === mViewGroup) {
+ setEnableForLayout(child as CellLayout, true)
+ }
+ }
+
+ override fun onChildViewRemoved(parent: View, child: View) {
+ if (parent === mViewGroup) {
+ setEnableForLayout(child as CellLayout, false)
+ }
+ }
+
+ protected open fun enableAccessibleDrag(enable: Boolean, dragObject: DragObject?) {
+ for (i in 0 until mViewGroup.childCount) {
+ setEnableForLayout(mViewGroup.getChildAt(i) as CellLayout, enable)
+ }
+ }
+
+ protected fun setEnableForLayout(layout: CellLayout, enable: Boolean) {
+ layout.setDragAndDropAccessibilityDelegate(
+ if (enable) mDelegateFactory.apply(layout) else null
+ )
+ }
+}
diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
index d0fc175..6f73e07 100644
--- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
@@ -29,9 +29,9 @@
import androidx.customview.widget.ExploreByTouchHelper;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
import java.util.List;
@@ -47,16 +47,17 @@
protected final CellLayout mView;
protected final Context mContext;
+ protected final ActivityContext mActivityContext;
protected final LauncherAccessibilityDelegate mDelegate;
- protected final DragLayer mDragLayer;
+ protected final BaseDragLayer<?> mDragLayer;
public DragAndDropAccessibilityDelegate(CellLayout forView) {
super(forView);
mView = forView;
mContext = mView.getContext();
- Launcher launcher = Launcher.getLauncher(mContext);
- mDelegate = launcher.getAccessibilityDelegate();
- mDragLayer = launcher.getDragLayer();
+ mActivityContext = ActivityContext.lookupContext(mContext);
+ mDelegate = (LauncherAccessibilityDelegate) mActivityContext.getAccessibilityDelegate();
+ mDragLayer = mActivityContext.getDragLayer();
}
@Override
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 0f4204f..c1264d6 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -41,7 +41,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -92,14 +91,14 @@
public class PrivateProfileManager extends UserProfileManager {
private static final String TAG = "PrivateProfileManager";
- private static final int EXPAND_COLLAPSE_DURATION = 800;
+ private static final int EXPAND_COLLAPSE_DURATION = 400;
private static final int SETTINGS_OPACITY_DURATION = 400;
private static final int TEXT_UNLOCK_OPACITY_DURATION = 300;
private static final int TEXT_LOCK_OPACITY_DURATION = 50;
private static final int APP_OPACITY_DURATION = 400;
private static final int MASK_VIEW_DURATION = 200;
private static final int APP_OPACITY_DELAY = 400;
- private static final int SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY = 400;
+ private static final int PILL_TRANSITION_DELAY = 400;
private static final int SETTINGS_OPACITY_DELAY = 400;
private static final int LOCK_TEXT_OPACITY_DELAY = 500;
private static final int MASK_VIEW_DELAY = 400;
@@ -109,6 +108,8 @@
private final Predicate<UserHandle> mPrivateProfileMatcher;
private final int mPsHeaderHeight;
private final int mFloatingMaskViewCornerRadius;
+ private final int mLockTextMarginStart;
+ private final int mLockTextMarginEnd;
private final RecyclerView.OnScrollListener mOnIdleScrollListener =
new RecyclerView.OnScrollListener() {
@Override
@@ -133,6 +134,11 @@
private Runnable mOnPSHeaderAdded;
@Nullable
private RelativeLayout mPSHeader;
+ @Nullable
+ private TextView mLockText;
+ @Nullable
+ private PrivateSpaceSettingsButton mPrivateSpaceSettingsButton;
+ @Nullable
private ConstraintLayout mFloatingMaskView;
private final String mLockedStateContentDesc;
private final String mUnLockedStateContentDesc;
@@ -155,6 +161,10 @@
.getString(R.string.ps_container_unlock_button_content_description);
mFloatingMaskViewCornerRadius = mAllApps.getContext().getResources().getDimensionPixelSize(
R.dimen.ps_floating_mask_corner_radius);
+ mLockTextMarginStart = mAllApps.getContext().getResources().getDimensionPixelSize(
+ R.dimen.ps_lock_icon_text_margin_start_expanded);
+ mLockTextMarginEnd = mAllApps.getContext().getResources().getDimensionPixelSize(
+ R.dimen.ps_lock_icon_text_margin_end_expanded);
}
/** Adds Private Space Header to the layout. */
@@ -354,20 +364,12 @@
/** Add Private Space Header view elements based upon {@link UserProfileState} */
public void bindPrivateSpaceHeaderViewElements(RelativeLayout parent) {
mPSHeader = parent;
+ Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Binding private space.");
+ updateView();
if (mOnPSHeaderAdded != null) {
MAIN_EXECUTOR.execute(mOnPSHeaderAdded);
mOnPSHeaderAdded = null;
}
- // Set the transition duration for the settings and lock button to animate.
- ViewGroup settingAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
- if (mReadyToAnimate) {
- enableLayoutTransition(settingAndLockGroup);
- } else {
- // Ensure any unwanted animations to not happen.
- settingAndLockGroup.setLayoutTransition(null);
- Log.d(TAG, "bindPrivateSpaceHeaderViewElements: removing transitions ");
- }
- updateView();
}
/** Update the states of the views that make up the header at the state it is called in. */
@@ -375,12 +377,15 @@
if (mPSHeader == null) {
return;
}
+ Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Updating view with state: "
+ + getCurrentState());
mPSHeader.setAlpha(1);
ViewGroup lockPill = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
assert lockPill != null;
- TextView lockText = lockPill.findViewById(R.id.lock_text);
- PrivateSpaceSettingsButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
- assert settingsButton != null;
+ mLockText = lockPill.findViewById(R.id.lock_text);
+ assert mLockText != null;
+ mPrivateSpaceSettingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
+ assert mPrivateSpaceSettingsButton != null;
//Add image for private space transitioning view
ImageView transitionView = mPSHeader.findViewById(R.id.ps_transition_image);
assert transitionView != null;
@@ -391,12 +396,18 @@
// Remove header from accessibility target when enabled.
mPSHeader.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- lockText.setVisibility(VISIBLE);
+ if (!mReadyToAnimate) {
+ // Don't set visibilities when animating as the animation will handle it.
+ mLockText.setVisibility(VISIBLE);
+ mLockText.setAlpha(1);
+ mLockText.setHorizontallyScrolling(false);
+ mPrivateSpaceSettingsButton.setVisibility(
+ isPrivateSpaceSettingsAvailable() ? VISIBLE : GONE);
+ }
lockPill.setVisibility(VISIBLE);
lockPill.setOnClickListener(view -> lockingAction(/* lock */ true));
lockPill.setContentDescription(mUnLockedStateContentDesc);
- settingsButton.setVisibility(isPrivateSpaceSettingsAvailable() ? VISIBLE : GONE);
transitionView.setVisibility(GONE);
}
case STATE_DISABLED -> {
@@ -406,12 +417,14 @@
mPSHeader.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
mPSHeader.setContentDescription(mLockedStateContentDesc);
- lockText.setVisibility(GONE);
+ mLockText.setVisibility(GONE);
+ mLockText.setAlpha(0);
+ mLockText.setHorizontallyScrolling(false);
lockPill.setVisibility(VISIBLE);
lockPill.setOnClickListener(view -> lockingAction(/* lock */ false));
lockPill.setContentDescription(mLockedStateContentDesc);
- settingsButton.setVisibility(GONE);
+ mPrivateSpaceSettingsButton.setVisibility(GONE);
transitionView.setVisibility(GONE);
}
case STATE_TRANSITION -> {
@@ -585,6 +598,51 @@
return alphaAnim;
}
+ private ValueAnimator animatePillTransition(boolean isExpanding) {
+ if (mLockText == null) {
+ return new ValueAnimator().setDuration(0);
+ }
+ mLockText.measure(0,0);
+ int currentWidth = mLockText.getWidth();
+ int fullWidth = mLockText.getMeasuredWidth();
+ float from = isExpanding ? 0 : currentWidth;
+ float to = isExpanding ? fullWidth : 0;
+ ValueAnimator pillAnim = ObjectAnimator.ofFloat(from, to);
+ pillAnim.setStartDelay(isExpanding ? PILL_TRANSITION_DELAY : 0);
+ pillAnim.setDuration(EXPAND_COLLAPSE_DURATION);
+ pillAnim.setInterpolator(Interpolators.STANDARD);
+ pillAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ float translation = (float) valueAnimator.getAnimatedValue();
+ float translationFraction = translation / fullWidth;
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) mLockText.getLayoutParams();
+ layoutParams.width = (int) translation;
+ layoutParams.setMarginStart((int) (mLockTextMarginStart * translationFraction));
+ layoutParams.setMarginEnd((int) (mLockTextMarginEnd * translationFraction));
+ mLockText.setLayoutParams(layoutParams);
+ mLockText.requestLayout();
+ }
+ });
+ pillAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!isExpanding) {
+ mLockText.setVisibility(GONE);
+ }
+ mLockText.setHorizontallyScrolling(false);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ mLockText.setHorizontallyScrolling(true);
+ mLockText.setVisibility(VISIBLE);
+ }
+ });
+ return pillAnim;
+ }
+
/**
* Using PropertySetter{@link PropertySetter}, we can update the view's attributes within an
* animation. At the moment, collapsing, setting alpha changes, and animating the text is done
@@ -596,22 +654,12 @@
}
if (mPSHeader == null) {
mOnPSHeaderAdded = () -> updatePrivateStateAnimator(expand);
- setAnimationRunning(false);
+ // Set animation to true, because onBind will be called after this return where we want
+ // the views to be updated accordingly so animation can happen.
+ setAnimationRunning(true);
return;
}
attachFloatingMaskView(expand);
- ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
- TextView lockText = mPSHeader.findViewById(R.id.lock_text);
- PrivateSpaceSettingsButton privateSpaceSettingsButton =
- mPSHeader.findViewById(R.id.ps_settings_button);
- if (settingsAndLockGroup.getLayoutTransition() == null) {
- // Set a new transition if the current ViewGroup does not already contain one as each
- // transition should only happen once when applied.
- enableLayoutTransition(settingsAndLockGroup);
- }
- settingsAndLockGroup.getLayoutTransition().setStartDelay(
- LayoutTransition.CHANGING,
- expand ? SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY : NO_DELAY);
PropertySetter headerSetter = new AnimatedPropertySetter();
headerSetter.add(updateSettingsGearAlpha(expand));
headerSetter.add(updateLockTextAlpha(expand));
@@ -626,8 +674,6 @@
? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN
: LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN,
mAllApps.getActiveRecyclerView());
- // Animate the collapsing of the text at the same time while updating lock button.
- lockText.setVisibility(expand ? VISIBLE : GONE);
setAnimationRunning(true);
}
@@ -646,10 +692,10 @@
: LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END,
mAllApps.getActiveRecyclerView());
Log.d(TAG, "updatePrivateStateAnimator: lockText visibility: "
- + lockText.getVisibility() + " lockTextAlpha: " + lockText.getAlpha());
+ + mLockText.getVisibility() + " lockTextAlpha: " + mLockText.getAlpha());
Log.d(TAG, "updatePrivateStateAnimator: settingsCog visibility: "
- + privateSpaceSettingsButton.getVisibility()
- + " settingsCogAlpha: " + privateSpaceSettingsButton.getAlpha());
+ + mPrivateSpaceSettingsButton.getVisibility()
+ + " settingsCogAlpha: " + mPrivateSpaceSettingsButton.getAlpha());
if (!expand) {
mAllApps.mAH.get(MAIN).mRecyclerView.removeItemDecoration(
mPrivateAppsSectionDecorator);
@@ -663,15 +709,19 @@
}));
if (expand) {
animatorSet.playTogether(animateAlphaOfIcons(true),
+ animatePillTransition(true),
translateFloatingMaskView(false));
} else {
+ AnimatorSet parallelSet = new AnimatorSet();
+ parallelSet.playTogether(animateAlphaOfIcons(false),
+ animatePillTransition(false));
if (isPrivateSpaceHidden()) {
- animatorSet.playSequentially(animateAlphaOfIcons(false),
+ animatorSet.playSequentially(parallelSet,
animateAlphaOfPrivateSpaceContainer(),
animateCollapseAnimation());
} else {
animatorSet.playSequentially(translateFloatingMaskView(true),
- animateAlphaOfIcons(false),
+ parallelSet,
animateCollapseAnimation());
}
}
@@ -702,7 +752,7 @@
/** Fades out the private space container. */
private ValueAnimator translateFloatingMaskView(boolean animateIn) {
if (!Flags.privateSpaceAddFloatingMaskView() || mFloatingMaskView == null) {
- return new ValueAnimator();
+ return new ValueAnimator().setDuration(0);
}
// Translate base on the height amount. Translates out on expand and in on collapse.
float floatingMaskViewHeight = getFloatingMaskViewHeight();
@@ -714,42 +764,19 @@
alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ if (mFloatingMaskView == null) {
+ return;
+ }
mFloatingMaskView.setTranslationY((float) valueAnimator.getAnimatedValue());
}
});
return alphaAnim;
}
- /** Animates the layout changes when the text of the button becomes visible/gone. */
- private void enableLayoutTransition(ViewGroup settingsAndLockGroup) {
- LayoutTransition settingsAndLockTransition = new LayoutTransition();
- settingsAndLockTransition.enableTransitionType(LayoutTransition.CHANGING);
- settingsAndLockTransition.setDuration(EXPAND_COLLAPSE_DURATION);
- settingsAndLockTransition.setInterpolator(LayoutTransition.CHANGING,
- Interpolators.STANDARD);
- settingsAndLockTransition.addTransitionListener(new LayoutTransition.TransitionListener() {
- @Override
- public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
- View view, int i) {
- Log.d(TAG, "updatePrivateStateAnimator: transition started: " + transition);
- }
- @Override
- public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
- View view, int i) {
- settingsAndLockGroup.setLayoutTransition(null);
- mReadyToAnimate = false;
- Log.d(TAG, "updatePrivateStateAnimator: transition finished: " + transition);
- }
- });
- settingsAndLockGroup.setLayoutTransition(settingsAndLockTransition);
- Log.d(TAG, "updatePrivateStateAnimator: setting transition: "
- + settingsAndLockTransition);
- }
-
/** Change the settings gear alpha when expanded or collapsed. */
private ValueAnimator updateSettingsGearAlpha(boolean expand) {
- if (mPSHeader == null) {
- return new ValueAnimator();
+ if (mPrivateSpaceSettingsButton == null || !isPrivateSpaceSettingsAvailable()) {
+ return new ValueAnimator().setDuration(0);
}
float from = expand ? 0 : 1;
float to = expand ? 1 : 0;
@@ -760,16 +787,21 @@
settingsAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
- mPSHeader.findViewById(R.id.ps_settings_button)
- .setAlpha((float) valueAnimator.getAnimatedValue());
+ mPrivateSpaceSettingsButton.setAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ });
+ settingsAlphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ mPrivateSpaceSettingsButton.setVisibility(VISIBLE);
}
});
return settingsAlphaAnim;
}
private ValueAnimator updateLockTextAlpha(boolean expand) {
- if (mPSHeader == null) {
- return new ValueAnimator();
+ if (mLockText == null) {
+ return new ValueAnimator().setDuration(0);
}
float from = expand ? 0 : 1;
float to = expand ? 1 : 0;
@@ -780,8 +812,7 @@
alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
- mPSHeader.findViewById(R.id.lock_text).setAlpha(
- (float) valueAnimator.getAnimatedValue());
+ mLockText.setAlpha((float) valueAnimator.getAnimatedValue());
}
});
return alphaAnim;
@@ -819,8 +850,22 @@
if (!Flags.privateSpaceAddFloatingMaskView()) {
return;
}
+ // Use getLocationOnScreen() as simply checking for mPSHeader.getBottom() is only relative
+ // to its parent.
+ int[] psHeaderLocation = new int[2];
+ mPSHeader.getLocationOnScreen(psHeaderLocation);
+ int psHeaderBottomY = psHeaderLocation[1] + mPsHeaderHeight;
+ // Calculate the topY of the floatingMaskView as if it was added.
+ int floatingMaskViewBottomBoxTopY =
+ (int) (mAllApps.getBottom() - getMainRecyclerView().getPaddingBottom());
+ // Don't attach if the header will be clipped by the floating mask view.
+ if (psHeaderBottomY > floatingMaskViewBottomBoxTopY) {
+ mFloatingMaskView = null;
+ return;
+ }
mFloatingMaskView = (FloatingMaskView) mAllApps.getLayoutInflater().inflate(
R.layout.private_space_mask_view, mAllApps, false);
+ assert mFloatingMaskView != null;
mAllApps.addView(mFloatingMaskView);
// Translate off the screen first if its collapsing so this header view isn't visible to
// user when animation starts.
diff --git a/src/com/android/launcher3/allapps/SectionDecorationHandler.java b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
index ac9b146..eaeb8bb 100644
--- a/src/com/android/launcher3/allapps/SectionDecorationHandler.java
+++ b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
@@ -25,7 +25,6 @@
import android.view.View;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
@@ -61,10 +60,10 @@
mContext = context;
mFillAlpha = fillAlpha;
- mFocusColor = ContextCompat.getColor(context,
- R.color.material_color_surface_bright); // UX recommended
- mFillColor = ContextCompat.getColor(context,
- R.color.material_color_surface_container_high); // UX recommended
+ mFocusColor = Themes.getAttrColor(context,
+ R.attr.materialColorSurfaceBright); // UX recommended
+ mFillColor = Themes.getAttrColor(context,
+ R.attr.materialColorSurfaceContainerHigh); // UX recommended
mIsTopLeftRound = isTopLeftRound;
mIsTopRightRound = isTopRightRound;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index d3c1a02..175ab4e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE;
@@ -1106,7 +1107,7 @@
}
private void updateItemLocationsInDatabaseBatch(boolean isBind) {
- FolderGridOrganizer verifier = new FolderGridOrganizer(
+ FolderGridOrganizer verifier = createFolderGridOrganizer(
mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
ArrayList<ItemInfo> items = new ArrayList<>();
@@ -1404,7 +1405,7 @@
@Override
public void onAdd(ItemInfo item, int rank) {
- FolderGridOrganizer verifier = new FolderGridOrganizer(
+ FolderGridOrganizer verifier = createFolderGridOrganizer(
mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
verifier.updateRankAndPos(item, rank);
mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 37a8d9b..3c4cf5a 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -99,7 +100,7 @@
mContext = folder.getContext();
mDeviceProfile = folder.mActivityContext.getDeviceProfile();
- mPreviewVerifier = new FolderGridOrganizer(mDeviceProfile);
+ mPreviewVerifier = createFolderGridOrganizer(mDeviceProfile);
mIsOpening = isOpening;
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
index 593673d..a7ab7b9 100644
--- a/src/com/android/launcher3/folder/FolderGridOrganizer.java
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -47,13 +47,20 @@
/**
* Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
*/
- public FolderGridOrganizer(DeviceProfile profile) {
- mMaxCountX = profile.numFolderColumns;
- mMaxCountY = profile.numFolderRows;
+ public FolderGridOrganizer(int maxCountX, int maxCountY) {
+ mMaxCountX = maxCountX;
+ mMaxCountY = maxCountY;
mMaxItemsPerPage = mMaxCountX * mMaxCountY;
}
/**
+ * Creates a FolderGridOrganizer for the given DeviceProfile
+ */
+ public static FolderGridOrganizer createFolderGridOrganizer(DeviceProfile profile) {
+ return new FolderGridOrganizer(profile.numFolderColumns, profile.numFolderRows);
+ }
+
+ /**
* Updates the organizer with the provided folder info
*/
public FolderGridOrganizer setFolderInfo(FolderInfo info) {
@@ -194,6 +201,7 @@
int row = rank / mCountX;
return col < PREVIEW_MAX_COLUMNS && row < PREVIEW_MAX_ROWS;
}
+ // If we have less than 4 items do this
return rank < MAX_NUM_ITEMS_IN_PREVIEW;
}
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 00636a3..e9859cf 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY;
@@ -223,7 +224,7 @@
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
- icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile());
+ icon.mPreviewVerifier = createFolderGridOrganizer(activity.getDeviceProfile());
icon.mPreviewVerifier.setFolderInfo(folderInfo);
icon.updatePreviewItems(false);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 8eaa0dc..1b5ef42 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -100,10 +101,21 @@
private boolean mViewsBound = false;
public FolderPagedView(Context context, AttributeSet attrs) {
+ this(
+ context,
+ attrs,
+ createFolderGridOrganizer(ActivityContext.lookupContext(context).getDeviceProfile())
+ );
+ }
+
+ public FolderPagedView(
+ Context context,
+ AttributeSet attrs,
+ FolderGridOrganizer folderGridOrganizer
+ ) {
super(context, attrs);
ActivityContext activityContext = ActivityContext.lookupContext(context);
- DeviceProfile profile = activityContext.getDeviceProfile();
- mOrganizer = new FolderGridOrganizer(profile);
+ mOrganizer = folderGridOrganizer;
mIsRtl = Utilities.isRtl(getResources());
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
index 778b32a..00f1c67 100644
--- a/src/com/android/launcher3/graphics/IconPalette.java
+++ b/src/com/android/launcher3/graphics/IconPalette.java
@@ -16,22 +16,15 @@
package com.android.launcher3.graphics;
-import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
-import android.util.Log;
-import androidx.core.graphics.ColorUtils;
-
-import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
/**
* Contains colors based on the dominant color of an icon.
*/
public class IconPalette {
-
- private static final boolean DEBUG = false;
private static final String TAG = "IconPalette";
private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
@@ -54,95 +47,4 @@
}
return result;
}
-
- /**
- * Resolves a color such that it has enough contrast to be used as the
- * color of an icon or text on the given background color.
- *
- * @return a color of the same hue with enough contrast against the background.
- *
- * This was copied from com.android.internal.util.NotificationColorUtil.
- */
- public static int resolveContrastColor(Context context, int color, int background) {
- final int resolvedColor = resolveColor(context, color);
-
- int contrastingColor = ensureTextContrast(resolvedColor, background);
-
- if (contrastingColor != resolvedColor) {
- if (DEBUG){
- Log.w(TAG, String.format(
- "Enhanced contrast of notification for %s " +
- "%s (over background) by changing #%s to %s",
- context.getPackageName(),
- contrastChange(resolvedColor, contrastingColor, background),
- Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor)));
- }
- }
- return contrastingColor;
- }
-
- /**
- * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
- *
- * This was copied from com.android.internal.util.NotificationColorUtil.
- */
- private static int resolveColor(Context context, int color) {
- if (color == Notification.COLOR_DEFAULT) {
- return context.getColor(R.color.notification_icon_default_color);
- }
- return color;
- }
-
- /** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */
- private static String contrastChange(int colorOld, int colorNew, int bg) {
- return String.format("from %.2f:1 to %.2f:1",
- ColorUtils.calculateContrast(colorOld, bg),
- ColorUtils.calculateContrast(colorNew, bg));
- }
-
- /**
- * Finds a text color with sufficient contrast over bg that has the same hue as the original
- * color.
- *
- * This was copied from com.android.internal.util.NotificationColorUtil.
- */
- private static int ensureTextContrast(int color, int bg) {
- return findContrastColor(color, bg, 4.5);
- }
- /**
- * Finds a suitable color such that there's enough contrast.
- *
- * @param fg the color to start searching from.
- * @param bg the color to ensure contrast against.
- * @param minRatio the minimum contrast ratio required.
- * @return a color with the same hue as {@param color}, potentially darkened to meet the
- * contrast ratio.
- *
- * This was copied from com.android.internal.util.NotificationColorUtil.
- */
- private static int findContrastColor(int fg, int bg, double minRatio) {
- if (ColorUtils.calculateContrast(fg, bg) >= minRatio) {
- return fg;
- }
-
- double[] lab = new double[3];
- ColorUtils.colorToLAB(bg, lab);
- double bgL = lab[0];
- ColorUtils.colorToLAB(fg, lab);
- double fgL = lab[0];
- boolean isBgDark = bgL < 50;
-
- double low = isBgDark ? fgL : 0, high = isBgDark ? 100 : fgL;
- final double a = lab[1], b = lab[2];
- for (int i = 0; i < 15 && high - low > 0.00001; i++) {
- final double l = (low + high) / 2;
- fg = ColorUtils.LABToColor(l, a, b);
- if (ColorUtils.calculateContrast(fg, bg) > minRatio) {
- if (isBgDark) high = l; else low = l;
- } else {
- if (isBgDark) low = l; else high = l;
- }
- }
- return ColorUtils.LABToColor(low, a, b);
- }
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 6088941..2408955 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -78,8 +78,6 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
@@ -106,6 +104,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -376,15 +375,6 @@
getApplicationContext(), providerInfo));
}
- private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
- WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
- info.providerName, info.user, mContext);
- if (widgetItem == null) {
- return;
- }
- inflateAndAddWidgets(info, widgetItem.widgetInfo);
- }
-
private void inflateAndAddWidgets(
LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
AppWidgetHostView view = mAppWidgetHost.createView(
@@ -468,17 +458,22 @@
break;
}
}
+ Map<ComponentKey, AppWidgetProviderInfo> widgetsMap = widgetProviderInfoMap;
for (ItemInfo itemInfo : currentAppWidgets) {
switch (itemInfo.itemType) {
case Favorites.ITEM_TYPE_APPWIDGET:
case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- if (widgetProviderInfoMap != null) {
- inflateAndAddWidgets(
- (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap);
- } else {
- inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
- dataModel.widgetsModel);
+ if (widgetsMap == null) {
+ widgetsMap = dataModel.widgetsModel.getWidgetsByComponentKey()
+ .entrySet()
+ .stream()
+ .filter(entry -> entry.getValue().widgetInfo != null)
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().widgetInfo
+ ));
}
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap);
break;
default:
break;
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index e6ade61..5faa2b8 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -52,6 +52,7 @@
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.ArrayList;
@@ -195,8 +196,8 @@
if (!WIDGETS_ENABLED) {
return;
}
- final List<WidgetsListBaseEntry> widgets =
- mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
+ List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
+ .build(mBgDataModel.widgetsModel.getWidgetsByPackageItem());
executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 312c6f4..269cb9f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -568,7 +568,7 @@
private void processFolderItems() {
// Sort the folder items, update ranks, and make sure all preview items are high res.
List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
- .stream().map(FolderGridOrganizer::new).toList();
+ .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList();
for (CollectionInfo collection : mBgDataModel.collections) {
if (!(collection instanceof FolderInfo folder)) {
continue;
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index 266ed0c..cf2cadc 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -24,6 +24,7 @@
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder
import java.util.Objects
import java.util.concurrent.Executor
import java.util.function.Predicate
@@ -78,7 +79,9 @@
}
fun bindUpdatedWidgets(dataModel: BgDataModel) {
- val widgets = dataModel.widgetsModel.getWidgetsListForPicker(app.context)
+ val widgets =
+ WidgetsListBaseEntriesBuilder(app.context)
+ .build(dataModel.widgetsModel.widgetsByPackageItem)
scheduleCallbackTask { it.bindAllWidgets(widgets) }
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 079987b..2febb22 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -109,7 +109,7 @@
final IconCache iconCache = app.getIconCache();
final String[] packages = mPackages;
- final int N = packages.length;
+ final int packageCount = packages.length;
final FlagOp flagOp;
final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
final Predicate<ItemInfo> matcher = mOp == OP_USER_AVAILABILITY_CHANGE
@@ -123,7 +123,7 @@
}
switch (mOp) {
case OP_ADD: {
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
iconCache.updateIconsForPkg(packages[i], mUser);
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
if (DEBUG) {
@@ -146,7 +146,7 @@
+ " Look for earlier AllAppsList logs to find more information.");
removedComponents.add(a.componentName);
})) {
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
iconCache.updateIconsForPkg(packages[i], mUser);
activitiesLists.put(packages[i],
appsList.updatePackage(context, packages[i], mUser));
@@ -156,13 +156,13 @@
flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
case OP_REMOVE: {
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
iconCache.removeIconsForPkg(packages[i], mUser);
}
// Fall through
}
case OP_UNAVAILABLE:
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
if (DEBUG) {
Log.d(TAG, getOpString() + ": removing package=" + packages[i]);
}
@@ -217,44 +217,44 @@
// For system apps, package manager send OP_UPDATE when an app is enabled.
final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
synchronized (dataModel) {
- dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+ dataModel.forAllWorkspaceItemInfos(mUser, itemInfo -> {
boolean infoUpdated = false;
boolean shortcutUpdated = false;
- ComponentName cn = si.getTargetComponent();
- if (cn != null && matcher.test(si)) {
+ ComponentName cn = itemInfo.getTargetComponent();
+ if (cn != null && matcher.test(itemInfo)) {
String packageName = cn.getPackageName();
- if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
- forceKeepShortcuts.add(si.id);
+ if (itemInfo.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
+ forceKeepShortcuts.add(itemInfo.id);
if (mOp == OP_REMOVE) {
return;
}
}
- if (si.isPromise() && isNewApkAvailable) {
+ if (itemInfo.isPromise() && isNewApkAvailable) {
boolean isTargetValid = !cn.getClassName().equals(
IconCache.EMPTY_CLASS_NAME);
- if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
List<ShortcutInfo> shortcut =
new ShortcutRequest(context, mUser)
.forPackage(cn.getPackageName(),
- si.getDeepShortcutId())
+ itemInfo.getDeepShortcutId())
.query(ShortcutRequest.PINNED);
if (shortcut.isEmpty()) {
isTargetValid = false;
if (DEBUG) {
Log.d(TAG, "Pinned Shortcut not found for updated"
- + " package=" + si.getTargetPackage());
+ + " package=" + itemInfo.getTargetPackage());
}
} else {
if (DEBUG) {
Log.d(TAG, "Found pinned shortcut for updated"
- + " package=" + si.getTargetPackage()
+ + " package=" + itemInfo.getTargetPackage()
+ ", isTargetValid=" + isTargetValid);
}
- si.updateFromDeepShortcutInfo(shortcut.get(0), context);
+ itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
infoUpdated = true;
}
} else if (isTargetValid) {
@@ -262,39 +262,39 @@
.isActivityEnabled(cn, mUser);
}
- if (!isTargetValid && (si.hasStatusFlag(
+ if (!isTargetValid && (itemInfo.hasStatusFlag(
FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
- || si.isArchived())) {
- if (updateWorkspaceItemIntent(context, si, packageName)) {
+ || itemInfo.isArchived())) {
+ if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
infoUpdated = true;
- } else if (si.hasPromiseIconUi()) {
- removedShortcuts.add(si.id);
+ } else if (itemInfo.hasPromiseIconUi()) {
+ removedShortcuts.add(itemInfo.id);
if (DEBUG) {
FileLog.w(TAG, "Removing restored shortcut promise icon"
+ " that no longer points to valid component."
- + " id=" + si.id
- + ", package=" + si.getTargetPackage()
- + ", status=" + si.status
- + ", isArchived=" + si.isArchived());
+ + " id=" + itemInfo.id
+ + ", package=" + itemInfo.getTargetPackage()
+ + ", status=" + itemInfo.status
+ + ", isArchived=" + itemInfo.isArchived());
}
return;
}
} else if (!isTargetValid) {
- removedShortcuts.add(si.id);
+ removedShortcuts.add(itemInfo.id);
if (DEBUG) {
FileLog.w(TAG, "Removing shortcut that no longer points to"
+ " valid component."
- + " id=" + si.id
- + " package=" + si.getTargetPackage()
- + " status=" + si.status);
+ + " id=" + itemInfo.id
+ + " package=" + itemInfo.getTargetPackage()
+ + " status=" + itemInfo.status);
}
return;
} else {
- si.status = WorkspaceItemInfo.DEFAULT;
+ itemInfo.status = WorkspaceItemInfo.DEFAULT;
infoUpdated = true;
}
} else if (isNewApkAvailable && removedComponents.contains(cn)) {
- if (updateWorkspaceItemIntent(context, si, packageName)) {
+ if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
infoUpdated = true;
}
}
@@ -304,7 +304,7 @@
packageName);
// TODO: See if we can migrate this to
// AppInfo#updateRuntimeFlagsForActivityTarget
- si.setProgressLevel(
+ itemInfo.setProgressLevel(
activities == null || activities.isEmpty()
? 100
: PackageManagerHelper.getLoadingProgress(
@@ -313,42 +313,42 @@
// In case an app is archived, we need to make sure that archived state
// in WorkspaceItemInfo is refreshed.
if (Flags.enableSupportForArchiving() && !activities.isEmpty()) {
- boolean newArchivalState = activities.get(
- 0).getActivityInfo().isArchived;
- if (newArchivalState != si.isArchived()) {
- si.runtimeStatusFlags ^= FLAG_ARCHIVED;
+ boolean newArchivalState = activities.get(0)
+ .getActivityInfo().isArchived;
+ if (newArchivalState != itemInfo.isArchived()) {
+ itemInfo.runtimeStatusFlags ^= FLAG_ARCHIVED;
infoUpdated = true;
}
}
- if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ if (itemInfo.itemType == Favorites.ITEM_TYPE_APPLICATION) {
if (activities != null && !activities.isEmpty()) {
- si.setNonResizeable(ApiWrapper.INSTANCE.get(context)
+ itemInfo.setNonResizeable(ApiWrapper.INSTANCE.get(context)
.isNonResizeableActivity(activities.get(0)));
}
- iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+ iconCache.getTitleAndIcon(itemInfo, itemInfo.usingLowResIcon());
infoUpdated = true;
}
}
- int oldRuntimeFlags = si.runtimeStatusFlags;
- si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
- if (si.runtimeStatusFlags != oldRuntimeFlags) {
+ int oldRuntimeFlags = itemInfo.runtimeStatusFlags;
+ itemInfo.runtimeStatusFlags = flagOp.apply(itemInfo.runtimeStatusFlags);
+ if (itemInfo.runtimeStatusFlags != oldRuntimeFlags) {
shortcutUpdated = true;
}
}
if (infoUpdated || shortcutUpdated) {
- updatedWorkspaceItems.add(si);
+ updatedWorkspaceItems.add(itemInfo);
}
- if (infoUpdated && si.id != ItemInfo.NO_ID) {
- taskController.getModelWriter().updateItemInDatabase(si);
+ if (infoUpdated && itemInfo.id != ItemInfo.NO_ID) {
+ taskController.getModelWriter().updateItemInDatabase(itemInfo);
}
});
for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
if (mUser.equals(widgetInfo.user)
&& widgetInfo.hasRestoreFlag(
- LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+ LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
&& packageSet.contains(widgetInfo.providerName.getPackageName())) {
widgetInfo.restoreStatus &=
~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
@@ -391,7 +391,7 @@
} else if (mOp == OP_UPDATE) {
// Mark disabled packages in the broadcast to be removed
final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < packageCount; i++) {
if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
if (DEBUG) {
Log.d(TAG, "OP_UPDATE:"
@@ -423,7 +423,7 @@
if (mOp == OP_ADD) {
// Load widgets for the new package. Changes due to app updates are handled through
// AppWidgetHost events, this is just to initialize the long-press options.
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
}
taskController.bindUpdatedWidgets(dataModel);
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 454ae96..35064cf 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -26,7 +26,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
@@ -39,9 +38,6 @@
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.WidgetSections;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.wm.shell.Flags;
import java.util.ArrayList;
@@ -54,7 +50,9 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
@@ -67,84 +65,31 @@
private static final boolean DEBUG = false;
/* Map of widgets and shortcuts that are tracked per package. */
- private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
+ private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
/**
- * Returns a list of {@link WidgetsListBaseEntry} filtered using given widget item filter. All
- * {@link WidgetItem}s in a single row are sorted (based on label and user), but the overall
- * list of {@link WidgetsListBaseEntry}s is not sorted.
- *
- * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
+ * Returns all widgets keyed by their component key.
*/
- public synchronized ArrayList<WidgetsListBaseEntry> getFilteredWidgetsListForPicker(
- Context context,
- Predicate<WidgetItem> widgetItemFilter) {
- if (!WIDGETS_ENABLED) {
- return new ArrayList<>();
- }
- ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
- AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
-
- for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
- PackageItemInfo pkgItem = entry.getKey();
- List<WidgetItem> widgetItems = entry.getValue()
- .stream()
- .filter(widgetItemFilter).toList();
- if (!widgetItems.isEmpty()) {
- String sectionName = (pkgItem.title == null) ? "" :
- indexer.computeSectionName(pkgItem.title);
- result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItems));
- result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems));
- }
- }
- return result;
- }
-
- /**
- * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row
- * are sorted (based on label and user), but the overall list of
- * {@link WidgetsListBaseEntry}s is not sorted.
- *
- * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
- */
- public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
- // return all items
- return getFilteredWidgetsListForPicker(context, /*widgetItemFilter=*/ item -> true);
- }
-
- /** Returns a mapping of packages to their widgets without static shortcuts. */
- public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
+ public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKey() {
if (!WIDGETS_ENABLED) {
return Collections.emptyMap();
}
- Map<PackageUserKey, List<WidgetItem>> packagesToWidgets = new HashMap<>();
- mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> {
- List<WidgetItem> widgets = widgetsAndShortcuts.stream()
- .filter(item -> item.widgetInfo != null)
- .collect(toList());
- if (widgets.size() > 0) {
- packagesToWidgets.put(
- new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user),
- widgets);
- }
- });
- return packagesToWidgets;
+ return mWidgetsByPackageItem.values().stream()
+ .flatMap(Collection::stream).distinct()
+ .collect(Collectors.toMap(
+ widget -> new ComponentKey(widget.componentName, widget.user),
+ Function.identity()
+ ));
}
/**
- * Returns a map of widget component keys to corresponding widget items. Excludes the
- * shortcuts.
+ * Returns widgets grouped by the package item that they should belong to.
*/
- public synchronized Map<ComponentKey, WidgetItem> getAllWidgetComponentsWithoutShortcuts() {
+ public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
if (!WIDGETS_ENABLED) {
return Collections.emptyMap();
}
- Map<ComponentKey, WidgetItem> widgetsMap = new HashMap<>();
- mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) ->
- widgetsAndShortcuts.stream().filter(item -> item.widgetInfo != null).forEach(
- item -> widgetsMap.put(new ComponentKey(item.componentName, item.user),
- item)));
- return widgetsMap;
+ return mWidgetsByPackageItem;
}
/**
@@ -210,14 +155,14 @@
if (packageUser == null) {
// Clear the list if this is an update on all widgets and shortcuts.
- mWidgetsList.clear();
+ mWidgetsByPackageItem.clear();
} else {
// Otherwise, only clear the widgets and shortcuts for the changed package.
- mWidgetsList.remove(packageItemInfoCache.getOrCreate(packageUser));
+ mWidgetsByPackageItem.remove(packageItemInfoCache.getOrCreate(packageUser));
}
// add and update.
- mWidgetsList.putAll(rawWidgetsShortcuts.stream()
+ mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
.filter(new WidgetFlagCheck())
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
@@ -237,7 +182,7 @@
return;
}
WidgetManagerHelper widgetManager = new WidgetManagerHelper(app.getContext());
- for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
+ for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsByPackageItem.entrySet()) {
if (packageNames.contains(entry.getKey().packageName)) {
List<WidgetItem> items = entry.getValue();
int count = items.size();
@@ -258,50 +203,6 @@
}
}
- private PackageItemInfo createPackageItemInfo(
- ComponentName providerName,
- UserHandle user,
- int category
- ) {
- if (category == NO_CATEGORY) {
- return new PackageItemInfo(providerName.getPackageName(), user);
- } else {
- return new PackageItemInfo("" , category, user);
- }
- }
-
- private IntSet getCategories(ComponentName providerName, Context context) {
- IntSet categories = WidgetSections.getWidgetsToCategory(context).get(providerName);
- if (categories != null) {
- return categories;
- }
- categories = new IntSet();
- categories.add(NO_CATEGORY);
- return categories;
- }
-
- public WidgetItem getWidgetProviderInfoByProviderName(
- ComponentName providerName, UserHandle user, Context context) {
- if (!WIDGETS_ENABLED) {
- return null;
- }
- IntSet categories = getCategories(providerName, context);
-
- // Checking if we have a provider in any of the categories.
- for (Integer category: categories) {
- PackageItemInfo key = createPackageItemInfo(providerName, user, category);
- List<WidgetItem> widgets = mWidgetsList.get(key);
- if (widgets != null) {
- return widgets.stream().filter(
- item -> item.componentName.equals(providerName)
- )
- .findFirst()
- .orElse(null);
- }
- }
- return null;
- }
-
/** Returns {@link PackageItemInfo} of a pending widget. */
public static PackageItemInfo newPendingItemInfo(Context context, ComponentName provider,
UserHandle user) {
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index fb463f7..7e139c3 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -64,6 +64,11 @@
/** All installed widgets. */
private List<WidgetsListBaseEntry> mAllWidgets = List.of();
+ /**
+ * Selectively chosen installed widgets which may be preferred for default display over the list
+ * of all widgets.
+ */
+ private List<WidgetsListBaseEntry> mDefaultWidgets = List.of();
/** Widgets that can be recommended to the users. */
private List<ItemInfo> mRecommendedWidgets = List.of();
@@ -194,6 +199,18 @@
public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
mAllWidgets = allWidgets;
+ mDefaultWidgets = List.of();
+ mChangeListener.onWidgetsBound();
+ }
+
+ /**
+ * Sets the list of widgets to be displayed by default and a complete list that can be displayed
+ * when user chooses to show all widgets.
+ */
+ public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets,
+ List<WidgetsListBaseEntry> defaultWidgets) {
+ mAllWidgets = allWidgets;
+ mDefaultWidgets = defaultWidgets;
mChangeListener.onWidgetsBound();
}
@@ -205,6 +222,14 @@
return mAllWidgets;
}
+ /**
+ * Returns a "selectively" chosen list of widgets that may be preferred to be shown by default
+ * instead of a complete list.
+ */
+ public List<WidgetsListBaseEntry> getDefaultWidgets() {
+ return mDefaultWidgets;
+ }
+
/** Returns a list of recommended widgets. */
public List<WidgetItem> getRecommendedWidgets() {
HashMap<ComponentKey, WidgetItem> allWidgetItems = new HashMap<>();
@@ -259,8 +284,10 @@
}
/** Gets the WidgetsListContentEntry for the currently selected header. */
- public WidgetsListContentEntry getSelectedAppWidgets(PackageUserKey packageUserKey) {
- return (WidgetsListContentEntry) mAllWidgets.stream()
+ public WidgetsListContentEntry getSelectedAppWidgets(PackageUserKey packageUserKey,
+ boolean useDefault) {
+ List<WidgetsListBaseEntry> widgets = useDefault ? mDefaultWidgets : mAllWidgets;
+ return (WidgetsListContentEntry) widgets.stream()
.filter(row -> row instanceof WidgetsListContentEntry
&& PackageUserKey.fromPackageItemInfo(row.mPkgItem).equals(packageUserKey))
.findAny()
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 6d6b3b6..f895b30 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -17,6 +17,9 @@
package com.android.launcher3.recyclerview
import android.content.Context
+import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.Companion.PROTECTED
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import androidx.recyclerview.widget.RecyclerView.ViewHolder
@@ -38,21 +41,18 @@
* [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
* will be added to [RecycledViewPool] on main thread.
*/
-class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
+class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : ActivityContext {
var hasWorkProfile = false
- private var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
+ @VisibleForTesting(otherwise = PROTECTED)
+ var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
/**
* Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
*/
- fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
+ fun preInflateAllAppsViewHolders(context: T) {
val appsView = context.appsView ?: return
val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
- val preInflateCount = getPreinflateCount(context)
- if (preInflateCount <= 0) {
- return
- }
// Create a separate context dedicated for all apps preinflation thread. The goal is to
// create a separate AssetManager obj internally to avoid lock contention with
@@ -77,34 +77,51 @@
null
) {
override fun setAppsPerRow(appsPerRow: Int) = Unit
+
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
}
+ preInflateAllAppsViewHolders(adapter, BaseAllAppsAdapter.VIEW_TYPE_ICON, activeRv) {
+ getPreinflateCount(context)
+ }
+ }
+
+ @VisibleForTesting(otherwise = PROTECTED)
+ fun preInflateAllAppsViewHolders(
+ adapter: RecyclerView.Adapter<*>,
+ viewType: Int,
+ parent: ViewGroup,
+ preInflationCountProvider: () -> Int
+ ) {
+ val preinflationCount = preInflationCountProvider.invoke()
+ if (preinflationCount <= 0) {
+ return
+ }
mCancellableTask?.cancel()
var task: CancellableTask<List<ViewHolder>>? = null
task =
CancellableTask(
{
val list: ArrayList<ViewHolder> = ArrayList()
- for (i in 0 until preInflateCount) {
+ for (i in 0 until preinflationCount) {
if (task?.canceled == true) {
break
}
- list.add(
- adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
- )
+ list.add(adapter.createViewHolder(parent, viewType))
}
list
},
MAIN_EXECUTOR,
{ viewHolders ->
- for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
+ // Run preInflationCountProvider again as the needed VH might have changed
+ val newPreinflationCount = preInflationCountProvider.invoke()
+ for (i in 0 until minOf(viewHolders.size, newPreinflationCount)) {
putRecycledView(viewHolders[i])
}
}
)
mCancellableTask = task
- VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask)
+ VIEW_PREINFLATION_EXECUTOR.execute(mCancellableTask)
}
/**
@@ -125,7 +142,7 @@
* app icons in size of one all apps pages, so that opening all apps don't need to inflate app
* icons.
*/
- fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
+ fun getPreinflateCount(context: T): Int {
var targetPreinflateCount =
PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
EXTRA_ICONS_COUNT
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 860f852..b1e82bb 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -77,6 +77,7 @@
private static final String TAG = "DisplayController";
private static final boolean DEBUG = false;
+ private static boolean sTaskbarModePreferenceStatusForTests = false;
private static boolean sTransientTaskbarStatusForTests = true;
// TODO(b/254119092) remove all logs with this tag
@@ -207,6 +208,14 @@
}
/**
+ * Enables respecting taskbar mode preference during test.
+ */
+ @VisibleForTesting
+ public static void enableTaskbarModePreferenceForTests(boolean enable) {
+ sTaskbarModePreferenceStatusForTests = enable;
+ }
+
+ /**
* Returns whether the taskbar is pinned in gesture navigation mode.
*/
public static boolean isPinnedTaskbar(Context context) {
@@ -465,7 +474,7 @@
if (navigationMode != NavigationMode.NO_BUTTON) {
return false;
}
- if (Utilities.isRunningInTestHarness()) {
+ if (Utilities.isRunningInTestHarness() && !sTaskbarModePreferenceStatusForTests) {
// TODO(b/258604917): Once ENABLE_TASKBAR_PINNING is enabled, remove usage of
// sTransientTaskbarStatusForTests and update test to directly
// toggle shared preference to switch transient taskbar on/off.
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index 12eff61..8ee799a 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -24,7 +24,10 @@
import android.content.Context;
import android.content.Intent;
+import androidx.annotation.VisibleForTesting;
+
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
/**
* Utility class for tracking if the screen is currently on or off
@@ -34,8 +37,7 @@
public static final MainThreadInitializedObject<ScreenOnTracker> INSTANCE =
new MainThreadInitializedObject<>(ScreenOnTracker::new);
- private final SimpleBroadcastReceiver mReceiver =
- new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive);
+ private final SimpleBroadcastReceiver mReceiver;
private final CopyOnWriteArrayList<ScreenOnListener> mListeners = new CopyOnWriteArrayList<>();
private final Context mContext;
@@ -44,8 +46,20 @@
private ScreenOnTracker(Context context) {
// Assume that the screen is on to begin with
mContext = context;
+ mReceiver = new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive);
+ init();
+ }
+
+ @VisibleForTesting
+ ScreenOnTracker(Context context, SimpleBroadcastReceiver receiver) {
+ mContext = context;
+ mReceiver = receiver;
+ init();
+ }
+
+ private void init() {
mIsScreenOn = true;
- mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
+ mReceiver.register(mContext, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
}
@Override
@@ -53,7 +67,8 @@
mReceiver.unregisterReceiverSafely(mContext);
}
- private void onReceive(Intent intent) {
+ @VisibleForTesting
+ void onReceive(Intent intent) {
String action = intent.getAction();
if (ACTION_SCREEN_ON.equals(action)) {
mIsScreenOn = true;
diff --git a/src/com/android/launcher3/util/ViewCache.java b/src/com/android/launcher3/util/ViewCache.java
index 98e6822..b98e977 100644
--- a/src/com/android/launcher3/util/ViewCache.java
+++ b/src/com/android/launcher3/util/ViewCache.java
@@ -21,6 +21,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.R;
/**
@@ -67,7 +69,8 @@
}
}
- private static class CacheEntry {
+ @VisibleForTesting
+ static class CacheEntry {
final int mMaxSize;
final View[] mViews;
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index e413d7f..2fa8bf4 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -24,6 +24,7 @@
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.util.ViewPool.Reusable;
@@ -43,9 +44,16 @@
public ViewPool(Context context, @Nullable ViewGroup parent,
int layoutId, int maxSize, int initialSize) {
+ this(LayoutInflater.from(context).cloneInContext(context),
+ parent, layoutId, maxSize, initialSize);
+ }
+
+ @VisibleForTesting
+ ViewPool(LayoutInflater inflater, @Nullable ViewGroup parent,
+ int layoutId, int maxSize, int initialSize) {
mLayoutId = layoutId;
mParent = parent;
- mInflater = LayoutInflater.from(context);
+ mInflater = inflater;
mPool = new Object[maxSize];
if (initialSize > 0) {
diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
index 104209e..856f4b3 100644
--- a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
@@ -110,7 +110,7 @@
}
View background = RoundedCornerEnforcement.findBackground(this);
if (background == null
- || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+ || RoundedCornerEnforcement.hasAppWidgetOptedOut(background)) {
resetRoundedCorners();
return;
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index 40c3984..71d8503 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -21,22 +21,16 @@
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-import java.util.Set;
-import java.util.WeakHashMap;
import java.util.function.IntConsumer;
/**
@@ -129,37 +123,4 @@
public void clearViews() {
super.clearViews();
}
-
- public static class ListenableHostView extends LauncherAppWidgetHostView {
-
- private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET;
-
- ListenableHostView(Context context) {
- super(context);
- }
-
- @Override
- public void updateAppWidget(RemoteViews remoteViews) {
- super.updateAppWidget(remoteViews);
- mUpdateListeners.forEach(Runnable::run);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(LauncherAppWidgetHostView.class.getName());
- }
-
- /**
- * Adds a callback to be run everytime the provided app widget updates.
- * @return a closable to remove this callback
- */
- public SafeCloseable addUpdateListener(Runnable callback) {
- if (mUpdateListeners == Collections.EMPTY_SET) {
- mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>());
- }
- mUpdateListeners.add(callback);
- return () -> mUpdateListeners.remove(callback);
- }
- }
}
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 1fb8c83..f499fca 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -37,6 +37,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
@@ -49,7 +50,6 @@
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
@@ -484,7 +484,8 @@
* Sets or unsets a flag the can change whether the widget host should be in the listening
* state.
*/
- private void setShouldListenFlag(int flag, boolean on) {
+ @VisibleForTesting
+ void setShouldListenFlag(int flag, boolean on) {
if (on) {
mFlags.updateAndGet(old -> old | flag);
} else {
diff --git a/src/com/android/launcher3/widget/ListenableHostView.java b/src/com/android/launcher3/widget/ListenableHostView.java
new file mode 100644
index 0000000..b809db0
--- /dev/null
+++ b/src/com/android/launcher3/widget/ListenableHostView.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.util.SafeCloseable;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+public class ListenableHostView extends LauncherAppWidgetHostView {
+
+ private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET;
+
+ ListenableHostView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ super.updateAppWidget(remoteViews);
+ mUpdateListeners.forEach(Runnable::run);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(LauncherAppWidgetHostView.class.getName());
+ }
+
+ /**
+ * Adds a callback to be run everytime the provided app widget updates.
+ * @return a closable to remove this callback
+ */
+ public SafeCloseable addUpdateListener(Runnable callback) {
+ if (mUpdateListeners == Collections.EMPTY_SET) {
+ mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>());
+ }
+ mUpdateListeners.add(callback);
+ return () -> mUpdateListeners.remove(callback);
+ }
+}
diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
index 1e46ffd..2e5e251 100644
--- a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
+++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
@@ -67,7 +67,7 @@
/**
* Check whether the app widget has opted out of the enforcement.
*/
- public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) {
+ public static boolean hasAppWidgetOptedOut(@NonNull View background) {
return background.getId() == android.R.id.background && background.getClipToOutline();
}
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index 9132b4f..23d0585 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -32,6 +32,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -57,8 +58,13 @@
final Context mContext;
public WidgetManagerHelper(Context context) {
+ this(context, AppWidgetManager.getInstance(context));
+ }
+
+ @VisibleForTesting
+ public WidgetManagerHelper(Context context, AppWidgetManager appWidgetManager) {
mContext = context;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
+ mAppWidgetManager = appWidgetManager;
}
/**
diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
index 398b1df..5ad9222 100644
--- a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
@@ -22,6 +22,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -52,6 +54,9 @@
}
}
+ @VisibleForTesting
+ CustomAppWidgetProviderInfo() {}
+
@Override
public void initSpans(Context context, InvariantDeviceProfile idp) {
mIsMinSizeFulfilled = Math.min(spanX, minSpanX) <= idp.numColumns
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 50012b3..faa5d12 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -30,6 +30,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -45,6 +46,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
@@ -62,9 +64,16 @@
private final HashMap<ComponentName, CustomWidgetPlugin> mPlugins;
private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
private Consumer<PackageUserKey> mWidgetRefreshCallback;
+ private final @NonNull AppWidgetManager mAppWidgetManager;
private CustomWidgetManager(Context context) {
+ this(context, AppWidgetManager.getInstance(context));
+ }
+
+ @VisibleForTesting
+ CustomWidgetManager(Context context, @NonNull AppWidgetManager widgetManager) {
mContext = context;
+ mAppWidgetManager = widgetManager;
mPlugins = new HashMap<>();
mCustomWidgets = new ArrayList<>();
PluginManagerWrapper.INSTANCE.get(context)
@@ -94,7 +103,7 @@
@Override
public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
- List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
+ List<AppWidgetProviderInfo> providers = mAppWidgetManager
.getInstalledProvidersForProfile(Process.myUserHandle());
if (providers.isEmpty()) return;
Parcel parcel = Parcel.obtain();
@@ -113,6 +122,12 @@
mCustomWidgets.removeIf(w -> w.getComponent().equals(cn));
}
+ @VisibleForTesting
+ @NonNull
+ Map<ComponentName, CustomWidgetPlugin> getPlugins() {
+ return mPlugins;
+ }
+
/**
* Inject a callback function to refresh the widgets.
*/
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt b/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt
new file mode 100644
index 0000000..1abe4da
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.model
+
+import android.content.Context
+import com.android.launcher3.compat.AlphabeticIndexCompat
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.data.PackageItemInfo
+import java.util.function.Predicate
+
+/**
+ * A helper class that builds the list of [WidgetsListBaseEntry]s used by the UI to display widgets.
+ */
+class WidgetsListBaseEntriesBuilder(val context: Context) {
+
+ /** Builds the widgets list entries in a format understandable by the widget picking UI. */
+ @JvmOverloads
+ fun build(
+ widgetsByPackageItem: Map<PackageItemInfo, List<WidgetItem>>,
+ widgetFilter: Predicate<WidgetItem> = Predicate<WidgetItem> { true },
+ ): List<WidgetsListBaseEntry> {
+ val indexer = AlphabeticIndexCompat(context)
+
+ return buildList {
+ for ((pkgItem, widgetItems) in widgetsByPackageItem.entries) {
+ val filteredWidgetItems = widgetItems.filter { widgetFilter.test(it) }
+ if (filteredWidgetItems.isNotEmpty()) {
+ // Enables fast scroll popup to show right characters in all locales.
+ val sectionName = pkgItem.title?.let { indexer.computeSectionName(it) } ?: ""
+
+ add(WidgetsListHeaderEntry.create(pkgItem, sectionName, filteredWidgetItems))
+ add(WidgetsListContentEntry(pkgItem, sectionName, filteredWidgetItems))
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 2e36583..21b7be4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -69,6 +69,7 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.search.SearchModeListener;
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -135,7 +136,7 @@
private WidgetsRecyclerView mCurrentTouchEventRecyclerView;
@Nullable
PersonalWorkPagedView mViewPager;
- private boolean mIsInSearchMode;
+ protected boolean mIsInSearchMode;
private boolean mIsNoWidgetsViewNeeded;
@Px
protected int mMaxSpanPerRow;
@@ -245,8 +246,12 @@
mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container);
mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar);
- mSearchBar.initialize(
- mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
+ mSearchBar.initialize(new WidgetsSearchDataProvider() {
+ @Override
+ public List<WidgetsListBaseEntry> getWidgets() {
+ return getWidgetsToDisplay();
+ }
+ }, /* searchModeListener= */ this);
}
private void setDeviceManagementResources() {
@@ -462,22 +467,28 @@
setTranslationShift(mTranslationShift);
}
+ /**
+ * Returns all displayable widgets.
+ */
+ protected List<WidgetsListBaseEntry> getWidgetsToDisplay() {
+ return mActivityContext.getPopupDataProvider().getAllWidgets();
+ }
+
@Override
public void onWidgetsBound() {
if (mIsInSearchMode) {
return;
}
- List<WidgetsListBaseEntry> allWidgets =
- mActivityContext.getPopupDataProvider().getAllWidgets();
+ List<WidgetsListBaseEntry> widgets = getWidgetsToDisplay();
AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
- primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+ primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(widgets);
if (mHasWorkProfile) {
mViewPager.setVisibility(VISIBLE);
mTabBar.setVisibility(VISIBLE);
AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
- workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+ workUserAdapterHolder.mWidgetsListAdapter.setWidgets(widgets);
onActivePageChanged(mViewPager.getCurrentPage());
} else {
onActivePageChanged(0);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index c84680d..c4c755a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -27,7 +27,9 @@
import android.graphics.Rect;
import android.os.Process;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -35,6 +37,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import android.widget.PopupMenu;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -83,6 +86,17 @@
private PackageUserKey mSelectedHeader;
private TextView mHeaderDescription;
+ /**
+ * A menu displayed for options (e.g. "show all widgets" filter) around widget lists in the
+ * picker.
+ */
+ protected View mWidgetOptionsMenu;
+ /**
+ * State of the options in the menu (if displayed to the user).
+ */
+ @Nullable
+ protected WidgetOptionsMenuState mWidgetOptionsMenuState = null;
+
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@@ -130,6 +144,9 @@
mHeaderTitle = mContent.findViewById(R.id.title);
mHeaderDescription = mContent.findViewById(R.id.widget_picker_description);
+ mWidgetOptionsMenu = mContent.findViewById(R.id.widget_picker_widget_options_menu);
+ setupWidgetOptionsMenu();
+
mRightPane = mContent.findViewById(R.id.right_pane);
mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
@@ -155,6 +172,40 @@
}
}
+ protected void setupWidgetOptionsMenu() {
+ mWidgetOptionsMenu.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mWidgetOptionsMenuState != null) {
+ PopupMenu popupMenu = new PopupMenu(mActivityContext, /*anchor=*/ v,
+ Gravity.END);
+ MenuItem menuItem = popupMenu.getMenu().add(
+ R.string.widget_picker_show_all_widgets_menu_item_title);
+ menuItem.setCheckable(true);
+ menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets);
+ menuItem.setOnMenuItemClickListener(
+ item -> onShowAllWidgetsMenuItemClick(item));
+ popupMenu.show();
+ }
+ }
+ });
+ }
+
+ private boolean onShowAllWidgetsMenuItemClick(MenuItem menuItem) {
+ mWidgetOptionsMenuState.showAllWidgets = !mWidgetOptionsMenuState.showAllWidgets;
+ menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets);
+
+ // Refresh widgets
+ onWidgetsBound();
+ if (mIsInSearchMode) {
+ mSearchBar.reset();
+ } else if (!mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader)) {
+ mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
+ mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop();
+ }
+ return true;
+ }
+
@Override
protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
if (enableCategorizedWidgetSuggestions()) {
@@ -234,6 +285,29 @@
}
@Override
+ protected List<WidgetsListBaseEntry> getWidgetsToDisplay() {
+ List<WidgetsListBaseEntry> allWidgets =
+ mActivityContext.getPopupDataProvider().getAllWidgets();
+ List<WidgetsListBaseEntry> defaultWidgets =
+ mActivityContext.getPopupDataProvider().getDefaultWidgets();
+
+ if (allWidgets.isEmpty() || defaultWidgets.isEmpty()) {
+ // no menu if there are no default widgets to show
+ mWidgetOptionsMenuState = null;
+ mWidgetOptionsMenu.setVisibility(GONE);
+ } else {
+ if (mWidgetOptionsMenuState == null) {
+ mWidgetOptionsMenuState = new WidgetOptionsMenuState();
+ }
+
+ mWidgetOptionsMenu.setVisibility(VISIBLE);
+ return mWidgetOptionsMenuState.showAllWidgets ? allWidgets : defaultWidgets;
+ }
+
+ return allWidgets;
+ }
+
+ @Override
public void onWidgetsBound() {
super.onWidgetsBound();
if (mRecommendedWidgetsCount == 0 && mSelectedHeader == null) {
@@ -435,8 +509,11 @@
final boolean isUserClick = mSelectedHeader != null
&& !getAccessibilityInitialFocusView().isAccessibilityFocused();
mSelectedHeader = selectedHeader;
- WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
- .getSelectedAppWidgets(selectedHeader);
+ WidgetsListContentEntry contentEntry =
+ mActivityContext.getPopupDataProvider().getSelectedAppWidgets(
+ selectedHeader, /*useDefault=*/
+ (mWidgetOptionsMenuState != null
+ && !mWidgetOptionsMenuState.showAllWidgets));
if (contentEntry == null || mRightPane == null) {
return;
@@ -570,4 +647,15 @@
*/
void onHeaderChanged(@NonNull PackageUserKey selectedHeader);
}
+
+ /**
+ * Holds the selection state of the options menu (if presented to the user).
+ */
+ protected static class WidgetOptionsMenuState {
+ /**
+ * UI state indicating whether to show default or all widgets.
+ * <p>If true, shows all widgets; else shows the default widgets.</p>
+ */
+ public boolean showAllWidgets = false;
+ }
}
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
index 65937b6..92caf3e 100644
--- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -26,7 +26,6 @@
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.R;
-import com.android.launcher3.popup.PopupDataProvider;
/**
* View for a search bar with an edit text with a cancel button.
@@ -51,7 +50,8 @@
}
@Override
- public void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener) {
+ public void initialize(WidgetsSearchDataProvider dataProvider,
+ SearchModeListener searchModeListener) {
mController = new WidgetsSearchBarController(
new SimpleWidgetsSearchAlgorithm(dataProvider),
mEditText, mCancelButton, searchModeListener);
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
index 613066a..0e88787 100644
--- a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
@@ -21,13 +21,13 @@
import android.os.Handler;
import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.search.SearchCallback;
import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
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.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
import java.util.ArrayList;
import java.util.List;
@@ -39,9 +39,9 @@
public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<WidgetsListBaseEntry> {
private final Handler mResultHandler;
- private final PopupDataProvider mDataProvider;
+ private final WidgetsSearchDataProvider mDataProvider;
- public SimpleWidgetsSearchAlgorithm(PopupDataProvider dataProvider) {
+ public SimpleWidgetsSearchAlgorithm(WidgetsSearchDataProvider dataProvider) {
mResultHandler = new Handler();
mDataProvider = dataProvider;
}
@@ -63,9 +63,9 @@
* Returns entries for all matched widgets
*/
public static ArrayList<WidgetsListBaseEntry> getFilteredWidgets(
- PopupDataProvider dataProvider, String input) {
+ WidgetsSearchDataProvider dataProvider, String input) {
ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
- dataProvider.getAllWidgets().stream()
+ dataProvider.getWidgets().stream()
.filter(entry -> entry instanceof WidgetsListHeaderEntry)
.forEach(headerEntry -> {
List<WidgetItem> matchedWidgetItems = filterWidgetItems(
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
index 44a5e80..ab504e7 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -16,7 +16,9 @@
package com.android.launcher3.widget.picker.search;
-import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
/**
* Interface for a widgets picker search bar.
@@ -25,7 +27,7 @@
/**
* Attaches a controller to the search bar which interacts with {@code searchModeListener}.
*/
- void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener);
+ void initialize(WidgetsSearchDataProvider dataProvider, SearchModeListener searchModeListener);
/**
* Clears search bar.
@@ -44,4 +46,15 @@
* Sets the vertical location, in pixels, of this search bar relative to its top position.
*/
void setTranslationY(float translationY);
+
+
+ /**
+ * Provides corpus from which search results must be returned.
+ */
+ interface WidgetsSearchDataProvider {
+ /**
+ * Returns the widgets from which the search should return the results.
+ */
+ List<WidgetsListBaseEntry> getWidgets();
+ }
}
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index fab3015..dc3b321 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -157,7 +157,6 @@
"get-overview-current-page-index";
public static final String REQUEST_GET_SPLIT_SELECTION_ACTIVE = "get-split-selection-active";
public static final String REQUEST_ENABLE_ROTATION = "enable_rotation";
- public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion";
public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared";
public static boolean sDebugTracing = false;
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 0538870..954dc8f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -47,14 +47,13 @@
abstract class FakeInvariantDeviceProfileTest {
protected lateinit var context: Context
- protected var inv: InvariantDeviceProfile? = null
- protected val info: Info = mock()
- protected var windowBounds: WindowBounds? = null
- protected var isMultiWindowMode: Boolean = false
- protected var transposeLayoutWithOrientation: Boolean = false
- protected var useTwoPanels: Boolean = false
- protected var isGestureMode: Boolean = true
- protected var isTransientTaskbar: Boolean = true
+ protected lateinit var inv: InvariantDeviceProfile
+ protected val info = mock<Info>()
+ protected lateinit var windowBounds: WindowBounds
+ private var transposeLayoutWithOrientation = false
+ private var useTwoPanels = false
+ private var isGestureMode = true
+ private var isTransientTaskbar = true
@Rule @JvmField val limitDevicesRule = LimitDevicesRule()
@@ -73,7 +72,7 @@
info,
windowBounds,
SparseArray(),
- isMultiWindowMode,
+ /*isMultiWindowMode=*/ false,
transposeLayoutWithOrientation,
useTwoPanels,
isGestureMode,
diff --git a/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
index 60a4197..5a26087 100644
--- a/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
@@ -17,12 +17,19 @@
package com.android.launcher3
import android.content.Context
+import android.content.ContextWrapper
+import android.graphics.Rect
+import android.graphics.RectF
import android.view.View
import android.view.ViewGroup
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.util.ActivityContextWrapper
-import org.junit.Assert.*
+import kotlin.random.Random
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,6 +37,10 @@
@RunWith(AndroidJUnit4::class)
class UtilitiesTest {
+ companion object {
+ const val SEED = 827
+ }
+
private lateinit var mContext: Context
@Before
@@ -94,4 +105,283 @@
assertTrue(Utilities.pointInView(view, -5f, -5f, 10f)) // Inside slop
assertFalse(Utilities.pointInView(view, 115f, 115f, 10f)) // Outside slop
}
+
+ @Test
+ fun testNumberBounding() {
+ assertEquals(887.99f, Utilities.boundToRange(887.99f, 0f, 1000f))
+ assertEquals(2.777f, Utilities.boundToRange(887.99f, 0f, 2.777f))
+ assertEquals(900f, Utilities.boundToRange(887.99f, 900f, 1000f))
+
+ assertEquals(9383667L, Utilities.boundToRange(9383667L, -999L, 9999999L))
+ assertEquals(9383668L, Utilities.boundToRange(9383667L, 9383668L, 9999999L))
+ assertEquals(42L, Utilities.boundToRange(9383667L, -999L, 42L))
+
+ assertEquals(345, Utilities.boundToRange(345, 2, 500))
+ assertEquals(400, Utilities.boundToRange(345, 400, 500))
+ assertEquals(300, Utilities.boundToRange(345, 2, 300))
+
+ val random = Random(SEED)
+ for (i in 1..300) {
+ val value = random.nextFloat()
+ val lowerBound = random.nextFloat()
+ val higherBound = lowerBound + random.nextFloat()
+
+ assertEquals(
+ "Utilities.boundToRange doesn't match Kotlin coerceIn",
+ value.coerceIn(lowerBound, higherBound),
+ Utilities.boundToRange(value, lowerBound, higherBound)
+ )
+ assertEquals(
+ "Utilities.boundToRange doesn't match Kotlin coerceIn",
+ value.toInt().coerceIn(lowerBound.toInt(), higherBound.toInt()),
+ Utilities.boundToRange(value.toInt(), lowerBound.toInt(), higherBound.toInt())
+ )
+ assertEquals(
+ "Utilities.boundToRange doesn't match Kotlin coerceIn",
+ value.toLong().coerceIn(lowerBound.toLong(), higherBound.toLong()),
+ Utilities.boundToRange(value.toLong(), lowerBound.toLong(), higherBound.toLong())
+ )
+ assertEquals(
+ "If the lower bound is higher than lower bound, it should return the lower bound",
+ higherBound,
+ Utilities.boundToRange(value, higherBound, lowerBound)
+ )
+ }
+ }
+
+ @Test
+ fun testTranslateOverlappingView() {
+ testConcentricOverlap()
+ leftDownCornerOverlap()
+ noOverlap()
+ }
+
+ /*
+ Test Case: Rectangle Contained Within Another Rectangle
+
+ +-------------+ <-- exclusionBounds
+ | |
+ | +-----+ |
+ | | | | <-- targetViewBounds
+ | | | |
+ | +-----+ |
+ | |
+ +-------------+
+ */
+ private fun testConcentricOverlap() {
+ val targetView = View(ContextWrapper(getApplicationContext()))
+ val targetViewBounds = Rect(40, 40, 60, 60)
+ val inclusionBounds = Rect(0, 0, 100, 100)
+ val exclusionBounds = Rect(30, 30, 70, 70)
+
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_RIGHT
+ )
+ assertEquals(30f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_LEFT
+ )
+ assertEquals(-30f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_DOWN
+ )
+ assertEquals(30f, targetView.translationY)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_UP
+ )
+ assertEquals(-30f, targetView.translationY)
+ }
+
+ /*
+ Test Case: Non-Overlapping Rectangles
+
+ +-----------------+ <-- targetViewBounds
+ | |
+ | |
+ +-----------------+
+
+ +-----------+ <-- exclusionBounds
+ | |
+ | |
+ +-----------+
+ */
+ private fun noOverlap() {
+ val targetView = View(ContextWrapper(getApplicationContext()))
+ val targetViewBounds = Rect(10, 10, 20, 20)
+
+ val inclusionBounds = Rect(0, 0, 100, 100)
+ val exclusionBounds = Rect(30, 30, 40, 40)
+
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_RIGHT
+ )
+ assertEquals(0f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_LEFT
+ )
+ assertEquals(0f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_DOWN
+ )
+ assertEquals(0f, targetView.translationY)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_UP
+ )
+ assertEquals(0f, targetView.translationY)
+ }
+
+ /*
+ Test Case: Rectangles Overlapping at Corners
+
+ +------------+ <-- exclusionBounds
+ | |
+ +-------+ |
+ | | | | <-- targetViewBounds
+ | +------------+
+ | |
+ +-------+
+ */
+ private fun leftDownCornerOverlap() {
+ val targetView = View(ContextWrapper(getApplicationContext()))
+ val targetViewBounds = Rect(20, 20, 30, 30)
+
+ val inclusionBounds = Rect(0, 0, 100, 100)
+ val exclusionBounds = Rect(25, 25, 35, 35)
+
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_RIGHT
+ )
+ assertEquals(15f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_LEFT
+ )
+ assertEquals(-5f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_DOWN
+ )
+ assertEquals(15f, targetView.translationY)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_UP
+ )
+ assertEquals(-5f, targetView.translationY)
+ }
+
+ @Test
+ fun trim() {
+ val expectedString = "Hello World"
+ assertEquals(expectedString, Utilities.trim("Hello World "))
+ // Basic trimming
+ assertEquals(expectedString, Utilities.trim(" Hello World "))
+ assertEquals(expectedString, Utilities.trim(" Hello World"))
+
+ // Non-breaking whitespace
+ assertEquals("Hello World", Utilities.trim("\u00A0\u00A0Hello World\u00A0\u00A0"))
+
+ // Whitespace combinations
+ assertEquals(expectedString, Utilities.trim("\t \r\n Hello World \n\r"))
+ assertEquals(expectedString, Utilities.trim("\nHello World "))
+
+ // Null input
+ assertEquals("", Utilities.trim(null))
+
+ // Empty String
+ assertEquals("", Utilities.trim(""))
+ }
+
+ @Test
+ fun getProgress() {
+ // Basic test
+ assertEquals(0.5f, Utilities.getProgress(50f, 0f, 100f), 0.001f)
+
+ // Negative values
+ assertEquals(0.5f, Utilities.getProgress(-20f, -50f, 10f), 0.001f)
+
+ // Outside of range
+ assertEquals(1.2f, Utilities.getProgress(120f, 0f, 100f), 0.001f)
+ }
+
+ @Test
+ fun scaleRectFAboutPivot() {
+ // Enlarge
+ var rectF = RectF(10f, 20f, 50f, 80f)
+ Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.5f)
+ assertEquals(RectF(0f, 5f, 60f, 95f), rectF)
+
+ // Shrink
+ rectF = RectF(10f, 20f, 50f, 80f)
+ Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 0.5f)
+ assertEquals(RectF(20f, 35f, 40f, 65f), rectF)
+
+ // No scale
+ rectF = RectF(10f, 20f, 50f, 80f)
+ Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.0f)
+ assertEquals(RectF(10f, 20f, 50f, 80f), rectF)
+ }
+
+ @Test
+ fun rotateBounds() {
+ var rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 0)
+ assertEquals(Rect(20, 70, 60, 80), rect)
+
+ rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 1)
+ assertEquals(Rect(70, 40, 80, 80), rect)
+
+ rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 2)
+ assertEquals(Rect(40, 20, 80, 30), rect)
+
+ rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 3)
+ assertEquals(Rect(20, 20, 30, 60), rect)
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt b/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt
new file mode 100644
index 0000000..e03ee46
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.accessibility
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.CellLayout
+import com.android.launcher3.DropTarget.DragObject
+import com.android.launcher3.dragndrop.DragOptions
+import com.android.launcher3.util.ActivityContextWrapper
+import java.util.function.Function
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.kotlin.any
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibleDragListenerAdapterTest {
+
+ private lateinit var mockViewGroup: ViewGroup
+ private lateinit var mockChildOne: CellLayout
+ private lateinit var mockChildTwo: CellLayout
+ private lateinit var mDelegateFactory: Function<CellLayout, DragAndDropAccessibilityDelegate>
+ private lateinit var adapter: AccessibleDragListenerAdapter
+ private lateinit var mContext: Context
+
+ @Before
+ fun setup() {
+ mContext = ActivityContextWrapper(getApplicationContext())
+ mockViewGroup = mock(ViewGroup::class.java)
+ mockChildOne = mock(CellLayout::class.java)
+ mockChildTwo = mock(CellLayout::class.java)
+ `when`(mockViewGroup.context).thenReturn(mContext)
+ `when`(mockViewGroup.childCount).thenReturn(2)
+ `when`(mockViewGroup.getChildAt(0)).thenReturn(mockChildOne)
+ `when`(mockViewGroup.getChildAt(1)).thenReturn(mockChildTwo)
+ // Mock Delegate factory
+ mDelegateFactory =
+ mock(Function::class.java) as Function<CellLayout, DragAndDropAccessibilityDelegate>
+ `when`(mDelegateFactory.apply(any()))
+ .thenReturn(mock(DragAndDropAccessibilityDelegate::class.java))
+ adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+ }
+
+ @Test
+ fun `onDragStart enables accessible drag for all view children`() {
+ // Create mock view children
+ val mockDragObject = mock(DragObject::class.java)
+ val mockDragOptions = mock(DragOptions::class.java)
+
+ // Action
+ adapter.onDragStart(mockDragObject, mockDragOptions)
+
+ // Assertion
+ verify(mockChildOne).setDragAndDropAccessibilityDelegate(any())
+ verify(mockChildTwo).setDragAndDropAccessibilityDelegate(any())
+ }
+
+ @Test
+ fun `onDragEnd removes the accessibility delegate`() {
+ // Action
+ adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+ adapter.onDragEnd()
+
+ // Assertion
+ verify(mockChildOne).setDragAndDropAccessibilityDelegate(null)
+ verify(mockChildTwo).setDragAndDropAccessibilityDelegate(null)
+ }
+
+ @Test
+ fun `onChildViewAdded sets enabled as true for childview`() {
+ // Action
+ adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+ adapter.onChildViewAdded(mockViewGroup, mockChildOne)
+
+ // Assertion
+ verify(mockChildOne).setDragAndDropAccessibilityDelegate(any())
+ }
+
+ @Test
+ fun `onChildViewRemoved sets enabled as false for childview`() {
+ // Action
+ adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+ adapter.onChildViewRemoved(mockViewGroup, mockChildOne)
+
+ // Assertion
+ verify(mockChildOne).setDragAndDropAccessibilityDelegate(null)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/accessibility/FolderAccessibilityHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/accessibility/FolderAccessibilityHelperTest.kt
new file mode 100644
index 0000000..1cbe1df
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/accessibility/FolderAccessibilityHelperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.accessibility // Use the original package
+
+// Imports
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.CellLayout
+import com.android.launcher3.folder.FolderPagedView
+import com.android.launcher3.util.ActivityContextWrapper
+import kotlin.math.min
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FolderAccessibilityHelperTest {
+
+ // Context
+ private lateinit var mContext: Context
+ // Mocks
+ @Mock private lateinit var mockParent: FolderPagedView
+ @Mock private lateinit var mockLayout: CellLayout
+
+ private var countX = 4
+ private var countY = 3
+ private var index = 1
+
+ // System under test
+ private lateinit var folderAccessibilityHelper: FolderAccessibilityHelper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mContext = ActivityContextWrapper(getApplicationContext())
+ `when`(mockLayout.parent).thenReturn(mockParent)
+ `when`(mockLayout.context).thenReturn(mContext)
+
+ // mStartPosition isn't recalculated after the constructor
+ // If you want to create new tests with different starting params,
+ // rebuild the folderAccessibilityHelper object
+ val countX = 4
+ val countY = 3
+ val index = 1
+ `when`(mockParent.indexOfChild(mockLayout)).thenReturn(index)
+ `when`(mockLayout.countX).thenReturn(countX)
+ `when`(mockLayout.countY).thenReturn(countY)
+
+ folderAccessibilityHelper = FolderAccessibilityHelper(mockLayout)
+ }
+
+ // Test for intersectsValidDropTarget()
+ @Test
+ fun testIntersectsValidDropTarget() {
+ // Setup
+ val id = 5
+ val allocatedContentSize = 20
+ // Make layout function public @VisibleForTesting
+ `when`(mockParent.allocatedContentSize).thenReturn(allocatedContentSize)
+
+ // Execute
+ val result = folderAccessibilityHelper.intersectsValidDropTarget(id)
+
+ // Verify
+ val expectedResult = min(id, allocatedContentSize - (index * countX * countY) - 1)
+ assertEquals(expectedResult, result)
+ }
+
+ // Test for getLocationDescriptionForIconDrop()
+ @Test
+ fun testGetLocationDescriptionForIconDrop() {
+ // Setup
+ val id = 5
+
+ // Execute
+ val result = folderAccessibilityHelper.getLocationDescriptionForIconDrop(id)
+
+ // Verify
+ val expectedResult = "Move to position ${id + (index * countX * countY) + 1}"
+ assertEquals(expectedResult, result)
+ }
+
+ // Test for getConfirmationForIconDrop()
+ @Test
+ fun testGetConfirmationForIconDrop() {
+ // Execute
+ val result =
+ folderAccessibilityHelper.getConfirmationForIconDrop(0) // Id doesn't matter here
+
+ // Verify
+ assertEquals("Item moved", result)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutMethodsTest.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutMethodsTest.kt
new file mode 100644
index 0000000..e8459d6
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutMethodsTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.celllayout
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CellLayoutMethodsTest {
+
+ @JvmField @Rule var cellLayoutBuilder = UnitTestCellLayoutBuilderRule()
+
+ @Test
+ fun pointToCellExact() {
+ val width = 1000
+ val height = 1000
+ val columns = 30
+ val rows = 30
+ val cl = cellLayoutBuilder.createCellLayout(columns, rows, false, width, height)
+
+ val res = intArrayOf(0, 0)
+ for (col in 0..<columns) {
+ for (row in 0..<rows) {
+ val x = (width / columns) * col
+ val y = (height / rows) * row
+ cl.pointToCellExact(x, y, res)
+ cl.pointToCellExact(x, y, res)
+ assertValues(col, res, row, columns, rows, width, height, x, y)
+ }
+ }
+
+ cl.pointToCellExact(-10, -10, res)
+ assertValues(0, res, 0, columns, rows, width, height, -10, -10)
+ cl.pointToCellExact(width + 10, height + 10, res)
+ assertValues(columns - 1, res, rows - 1, columns, rows, width, height, -10, -10)
+ }
+
+ private fun assertValues(
+ col: Int,
+ res: IntArray,
+ row: Int,
+ columns: Int,
+ rows: Int,
+ width: Int,
+ height: Int,
+ x: Int,
+ y: Int
+ ) {
+ assert(col == res[0] && row == res[1]) {
+ "Cell Layout with values (c= $columns, r= $rows, w= $width, h= $height) didn't mapped correctly the pixels ($x, $y) to the cells ($col, $row) with result (${res[0]}, ${res[1]})"
+ }
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index 8a9711d..30953cc 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -144,8 +144,8 @@
public ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX,
int spanY, int minSpanX, int minSpanY, boolean isMulti) {
- CellLayout cl = mCellLayoutBuilder.createCellLayout(board.getWidth(), board.getHeight(),
- isMulti);
+ CellLayout cl = mCellLayoutBuilder.createCellLayoutDefaultSize(board.getWidth(),
+ board.getHeight(), isMulti);
// The views have to be sorted or the result can vary
board.getIcons()
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
index b63966d..f624be1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
@@ -64,11 +64,21 @@
dp.inv.numRows = prevNumRows
}
- fun createCellLayout(width: Int, height: Int, isMulti: Boolean): CellLayout {
+ fun createCellLayoutDefaultSize(columns: Int, rows: Int, isMulti: Boolean): CellLayout {
+ return createCellLayout(columns, rows, isMulti)
+ }
+
+ fun createCellLayout(
+ columns: Int,
+ rows: Int,
+ isMulti: Boolean,
+ width: Int = 1000,
+ height: Int = 1000
+ ): CellLayout {
val dp = getDeviceProfile()
// modify the device profile.
- dp.inv.numColumns = if (isMulti) width / 2 else width
- dp.inv.numRows = height
+ dp.inv.numColumns = if (isMulti) columns / 2 else columns
+ dp.inv.numRows = rows
dp.cellLayoutBorderSpacePx = Point(0, 0)
val cl =
if (isMulti) MultipageCellLayout(getWrappedContext(applicationContext, dp))
@@ -76,8 +86,8 @@
// I put a very large number for width and height so that all the items can fit, it doesn't
// need to be exact, just bigger than the sum of cell border
cl.measure(
- View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY)
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
)
return cl
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
new file mode 100644
index 0000000..71f7d47
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Context
+import android.os.UserHandle
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.LimitDevicesRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.data.PackageItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.IntSet
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.android.launcher3.widget.WidgetSections
+import com.android.launcher3.widget.WidgetSections.NO_CATEGORY
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC])
+@RunWith(AndroidJUnit4::class)
+class WidgetsModelTest {
+ @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+ @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var appWidgetManager: AppWidgetManager
+ @Mock private lateinit var app: LauncherAppState
+ @Mock private lateinit var iconCacheMock: IconCache
+
+ private lateinit var context: Context
+ private lateinit var idp: InvariantDeviceProfile
+ private lateinit var underTest: WidgetsModel
+
+ private var widgetSectionCategory: Int = 0
+ private lateinit var appAPackage: String
+
+ @Before
+ fun setUp() {
+ val appContext: Context = ApplicationProvider.getApplicationContext()
+ idp = InvariantDeviceProfile.INSTANCE[appContext]
+
+ context =
+ object : ActivityContextWrapper(ApplicationProvider.getApplicationContext()) {
+ override fun getSystemService(name: String): Any? {
+ if (name == "appwidget") {
+ return appWidgetManager
+ }
+ return super.getSystemService(name)
+ }
+
+ override fun getDeviceProfile(): DeviceProfile {
+ return idp.getDeviceProfile(applicationContext).copy(applicationContext)
+ }
+ }
+
+ whenever(iconCacheMock.getTitleNoCache(any<LauncherAppWidgetProviderInfo>()))
+ .thenReturn("title")
+ whenever(app.iconCache).thenReturn(iconCacheMock)
+ whenever(app.context).thenReturn(context)
+ whenever(app.invariantDeviceProfile).thenReturn(idp)
+
+ val widgetToCategoryEntry: Map.Entry<ComponentName, IntSet> =
+ WidgetSections.getWidgetsToCategory(context).entries.first()
+ widgetSectionCategory = widgetToCategoryEntry.value.first()
+ val appAWidgetComponent = widgetToCategoryEntry.key
+ appAPackage = appAWidgetComponent.packageName
+
+ whenever(appWidgetManager.getInstalledProvidersForProfile(any()))
+ .thenReturn(
+ listOf(
+ // First widget from widget sections xml
+ createAppWidgetProviderInfo(appAWidgetComponent),
+ // A widget that belongs to same package as the widget from widget sections
+ // xml, but, because it's not mentioned in xml, it would be included in its
+ // own package section.
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(appAPackage, APP_A_TEST_WIDGET_NAME)
+ ),
+ // A widget in different package (none of that app's widgets are in widget
+ // sections xml)
+ createAppWidgetProviderInfo(AppBTestWidgetComponent),
+ )
+ )
+
+ val userCache = spy(UserCache.INSTANCE.get(context))
+ whenever(userCache.userProfiles).thenReturn(listOf(UserHandle.CURRENT))
+
+ underTest = WidgetsModel()
+ }
+
+ @Test
+ fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
+ loadWidgets()
+
+ val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
+
+ // expect 3 package items
+ // one for the custom section with widget from appA
+ // one for package section for second widget from appA (that wasn't listed in xml)
+ // and one for package section for appB
+ assertThat(packages).hasSize(3)
+
+ // Each package item when used as a key is distinct (i.e. even if appA is split into custom
+ // package and owner package section, each of them is a distinct key). This ensures that
+ // clicking on a custom widget section doesn't take user to app package section.
+ val distinctPackageUserKeys =
+ packages.map { PackageUserKey.fromPackageItemInfo(it.key) }.distinct()
+ assertThat(distinctPackageUserKeys).hasSize(3)
+
+ val customSections = packages.filter { it.key.widgetCategory == widgetSectionCategory }
+ assertThat(customSections).hasSize(1)
+ val widgetsInCustomSection = customSections.entries.first().value
+ assertThat(widgetsInCustomSection).hasSize(1)
+
+ val packageSections = packages.filter { it.key.widgetCategory == NO_CATEGORY }
+ assertThat(packageSections).hasSize(2)
+
+ // App A's package section
+ val appAPackageSection = packageSections.filter { it.key.packageName == appAPackage }
+ assertThat(appAPackageSection).hasSize(1)
+ val widgetsInAppASection = appAPackageSection.entries.first().value
+ assertThat(widgetsInAppASection).hasSize(1)
+
+ // App B's package section
+ val appBPackageSection =
+ packageSections.filter { it.key.packageName == AppBTestWidgetComponent.packageName }
+ assertThat(appBPackageSection).hasSize(1)
+ val widgetsInAppBSection = appBPackageSection.entries.first().value
+ assertThat(widgetsInAppBSection).hasSize(1)
+ }
+
+ @Test
+ fun widgetComponentMap_returnsWidgets() {
+ loadWidgets()
+
+ val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
+
+ assertThat(widgetsByComponentKey).hasSize(3)
+ widgetsByComponentKey.forEach { entry ->
+ assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
+ }
+ }
+
+ @Test
+ fun widgets_noData_returnsEmpty() {
+ // no loadWidgets()
+
+ assertThat(underTest.widgetsByComponentKey).isEmpty()
+ }
+
+ private fun loadWidgets() {
+ val latch = CountDownLatch(1)
+ Executors.MODEL_EXECUTOR.execute {
+ underTest.update(app, /* packageUser= */ null)
+ latch.countDown()
+ }
+ if (!latch.await(LOAD_WIDGETS_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
+ fail("Timed out waiting widgets to load")
+ }
+ }
+
+ companion object {
+ // Another widget within app A
+ private const val APP_A_TEST_WIDGET_NAME = "MyProvider"
+
+ private val AppBTestWidgetComponent: ComponentName =
+ ComponentName.createRelative("com.test.package", "TestProvider")
+
+ private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
new file mode 100644
index 0000000..3dd8dbc
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.pm
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.LauncherApps
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.PROMISE_ICON_IDS
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InstallSessionHelperTest {
+
+ private val launcherModelHelper = LauncherModelHelper()
+ private val sandboxContext = spy(launcherModelHelper.sandboxContext)
+ private val packageManager = sandboxContext.packageManager
+ private val expectedAppPackage = "expectedAppPackage"
+ private val expectedInstallerPackage = "expectedInstallerPackage"
+ private val mockPackageInstaller: PackageInstaller = mock()
+
+ private lateinit var installSessionHelper: InstallSessionHelper
+ private lateinit var launcherApps: LauncherApps
+
+ @Before
+ fun setup() {
+ whenever(packageManager.packageInstaller).thenReturn(mockPackageInstaller)
+ whenever(sandboxContext.packageName).thenReturn(expectedInstallerPackage)
+ launcherApps = sandboxContext.spyService(LauncherApps::class.java)
+ installSessionHelper = InstallSessionHelper(sandboxContext)
+ }
+
+ @Test
+ fun `getActiveSessions fetches verified install sessions from LauncherApps`() {
+ // Given
+ val expectedVerifiedSession1 =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 0
+ installerPackageName = expectedInstallerPackage
+ appPackageName = expectedAppPackage
+ userId = 0
+ }
+ val expectedVerifiedSession2 =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 1
+ installerPackageName = expectedInstallerPackage
+ appPackageName = "app2"
+ userId = 0
+ }
+ val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
+ whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
+ // When
+ val actualSessions = installSessionHelper.getActiveSessions()
+ // Then
+ assertThat(actualSessions.values.toList()).isEqualTo(expectedSessions)
+ }
+
+ @Test
+ fun `getActiveSessionInfo fetches verified install sessions for given user and pkg`() {
+ // Given
+ val expectedVerifiedSession =
+ PackageInstaller.SessionInfo().apply {
+ installerPackageName = expectedInstallerPackage
+ appPackageName = expectedAppPackage
+ userId = 0
+ }
+ whenever(launcherApps.allPackageInstallerSessions)
+ .thenReturn(listOf(expectedVerifiedSession))
+ // When
+ val actualSession =
+ installSessionHelper.getActiveSessionInfo(UserHandle(0), expectedAppPackage)
+ // Then
+ assertThat(actualSession).isEqualTo(expectedVerifiedSession)
+ }
+
+ @Test
+ fun `getVerifiedSessionInfo verifies and returns session for given id`() {
+ // Given
+ val expectedSession =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 1
+ installerPackageName = expectedInstallerPackage
+ appPackageName = expectedAppPackage
+ userId = 0
+ }
+ whenever(mockPackageInstaller.getSessionInfo(1)).thenReturn(expectedSession)
+ // When
+ val actualSession = installSessionHelper.getVerifiedSessionInfo(1)
+ // Then
+ assertThat(actualSession).isEqualTo(expectedSession)
+ }
+
+ @Test
+ fun `isTrustedPackage returns true if LauncherApps finds ApplicationInfo`() {
+ // Given
+ val expectedApplicationInfo =
+ ApplicationInfo().apply {
+ flags = flags or FLAG_INSTALLED
+ enabled = true
+ }
+ doReturn(expectedApplicationInfo)
+ .whenever(launcherApps)
+ .getApplicationInfo(expectedAppPackage, ApplicationInfo.FLAG_SYSTEM, UserHandle(0))
+ // When
+ val actualResult = installSessionHelper.isTrustedPackage(expectedAppPackage, UserHandle(0))
+ // Then
+ assertThat(actualResult).isTrue()
+ }
+
+ @Test
+ fun `getAllVerifiedSessions verifies and returns all active install sessions`() {
+ // Given
+ val expectedVerifiedSession1 =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 0
+ installerPackageName = expectedInstallerPackage
+ appPackageName = expectedAppPackage
+ userId = 0
+ }
+ val expectedVerifiedSession2 =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 1
+ installerPackageName = expectedInstallerPackage
+ appPackageName = "app2"
+ userId = 0
+ }
+ val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
+ whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
+ // When
+ val actualSessions = installSessionHelper.allVerifiedSessions
+ // Then
+ assertThat(actualSessions).isEqualTo(expectedSessions)
+ }
+
+ @Test
+ fun `promiseIconAddedForId returns true if there is a promiseIcon with the session id`() {
+ // Given
+ val expectedIdString = IntArray().apply { add(1) }.toConcatString()
+ LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
+ val expectedSession =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 1
+ installerPackageName = expectedInstallerPackage
+ appPackageName = "app2"
+ userId = 0
+ }
+ whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
+ // When
+ var actualResult = false
+ TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+ actualResult = installSessionHelper.promiseIconAddedForId(1)
+ }
+ // Then
+ assertThat(actualResult).isTrue()
+ }
+
+ @Test
+ fun `removePromiseIconId removes promiseIconId for given Session id`() {
+ // Given
+ val expectedIdString = IntArray().apply { add(1) }.toConcatString()
+ LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
+ val expectedSession =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 1
+ installerPackageName = expectedInstallerPackage
+ appPackageName = "app2"
+ userId = 0
+ }
+ whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
+ // When
+ var actualResult = true
+ TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+ installSessionHelper.removePromiseIconId(1)
+ actualResult = installSessionHelper.promiseIconAddedForId(1)
+ }
+ // Then
+ assertThat(actualResult).isFalse()
+ }
+
+ @Test
+ fun `tryQueuePromiseAppIcon will update promise icon ids from eligible sessions`() {
+ // Given
+ val expectedIdString = IntArray().apply { add(1) }.toConcatString()
+ LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
+ val expectedSession =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 1
+ installerPackageName = expectedInstallerPackage
+ appPackageName = "appPackage"
+ userId = 0
+ appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
+ appLabel = "appLabel"
+ installReason = PackageManager.INSTALL_REASON_USER
+ }
+ whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
+ // When
+ var wasPromiseIconAdded = false
+ var actualPromiseIconIds = ""
+ TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+ installSessionHelper.removePromiseIconId(1)
+ installSessionHelper.tryQueuePromiseAppIcon(expectedSession)
+ wasPromiseIconAdded = installSessionHelper.promiseIconAddedForId(1)
+ actualPromiseIconIds = LauncherPrefs.get(sandboxContext).get(PROMISE_ICON_IDS)
+ }
+ // Then
+ assertThat(wasPromiseIconAdded).isTrue()
+ assertThat(actualPromiseIconIds).isEqualTo(expectedIdString)
+ }
+
+ @Test
+ fun `verifySessionInfo is true if can verify given SessionInfo`() {
+ // Given
+ val expectedIdString = IntArray().apply { add(1) }.toConcatString()
+ LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
+ val expectedSession =
+ PackageInstaller.SessionInfo().apply {
+ sessionId = 1
+ installerPackageName = expectedInstallerPackage
+ appPackageName = "appPackage"
+ userId = 0
+ appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
+ appLabel = "appLabel"
+ installReason = PackageManager.INSTALL_REASON_USER
+ }
+ whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
+ // When
+ var actualResult = false
+ TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+ actualResult = installSessionHelper.verifySessionInfo(expectedSession)
+ }
+ // Then
+ assertThat(actualResult).isTrue()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt
new file mode 100644
index 0000000..8204313
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.recyclerview
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors
+import com.android.launcher3.views.ActivityContext
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {
+
+ private lateinit var underTest: AllAppsRecyclerViewPool<T>
+ private lateinit var adapter: RecyclerView.Adapter<*>
+
+ @Mock private lateinit var parent: ViewGroup
+ @Mock private lateinit var itemView: View
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = spy(AllAppsRecyclerViewPool())
+ adapter =
+ object : RecyclerView.Adapter<ViewHolder>() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+ object : ViewHolder(itemView) {}
+
+ override fun getItemCount() = 0
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
+ }
+ underTest.setMaxRecycledViews(VIEW_TYPE, 20)
+ }
+
+ @Test
+ fun preinflate_success() {
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+
+ awaitTasksCompleted()
+ assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10)
+ }
+
+ @Test
+ fun preinflate_not_triggered() {
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 0 }
+
+ awaitTasksCompleted()
+ assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
+ }
+
+ @Test
+ fun preinflate_cancel_before_runOnMainThread() {
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+ assertThat(underTest.mCancellableTask!!.canceled).isFalse()
+
+ underTest.clear()
+
+ awaitTasksCompleted()
+ verify(underTest, never()).putRecycledView(any(ViewHolder::class.java))
+ assertThat(underTest.mCancellableTask!!.canceled).isTrue()
+ assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
+ }
+
+ @Test
+ fun preinflate_cancel_after_run() {
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+ assertThat(underTest.mCancellableTask!!.canceled).isFalse()
+ awaitTasksCompleted()
+
+ underTest.clear()
+
+ verify(underTest, times(10)).putRecycledView(any(ViewHolder::class.java))
+ assertThat(underTest.mCancellableTask!!.canceled).isTrue()
+ assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
+ }
+
+ private fun awaitTasksCompleted() {
+ Executors.VIEW_PREINFLATION_EXECUTOR.submit<Any> { null }.get()
+ Executors.MAIN_EXECUTOR.submit<Any> { null }.get()
+ }
+
+ companion object {
+ private const val VIEW_TYPE: Int = 4
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
index 191d284..4217d22 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.util;
+import android.R;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.ContextThemeWrapper;
@@ -39,11 +40,16 @@
private final MyDragLayer mMyDragLayer;
public ActivityContextWrapper(Context base) {
- super(base, android.R.style.Theme_DeviceDefault);
+ this(base, R.style.Theme_DeviceDefault);
+ }
+
+ public ActivityContextWrapper(Context base, int theme) {
+ super(base, theme);
mProfile = InvariantDeviceProfile.INSTANCE.get(base).getDeviceProfile(base).copy(base);
mMyDragLayer = new MyDragLayer(this);
}
+
@Override
public BaseDragLayer getDragLayer() {
return mMyDragLayer;
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ItemInfoMatcherTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ItemInfoMatcherTest.kt
new file mode 100644
index 0000000..daba024
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ItemInfoMatcherTest.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ItemInfoMatcher.ofShortcutKeys
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ItemInfoMatcherTest {
+
+ @Test
+ fun `ofUser returns Predicate for ItemInfo containing given UserHandle`() {
+ val expectedItemInfo = ItemInfo().apply { user = UserHandle(11) }
+ val unexpectedItemInfo = ItemInfo().apply { user = UserHandle(0) }
+ val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+
+ val predicate = ItemInfoMatcher.ofUser(UserHandle(11))
+ val actualResults = itemInfoStream.filter(predicate).toList()
+
+ assertThat(actualResults).containsExactly(expectedItemInfo)
+ }
+
+ @Test
+ fun `ofComponents returns Predicate for ItemInfo containing target Component and UserHandle`() {
+ // Given
+ val expectedUserHandle = UserHandle(0)
+ val expectedComponentName = ComponentName("expectedPackage", "expectedClass")
+ val expectedItemInfo = spy(ItemInfo())
+ expectedItemInfo.user = expectedUserHandle
+ whenever(expectedItemInfo.targetComponent).thenReturn(expectedComponentName)
+
+ val unexpectedComponentName = ComponentName("unexpectedPackage", "unexpectedClass")
+ val unexpectedItemInfo1 = spy(ItemInfo())
+ unexpectedItemInfo1.user = expectedUserHandle
+ whenever(unexpectedItemInfo1.targetComponent).thenReturn(unexpectedComponentName)
+
+ val unexpectedItemInfo2 = spy(ItemInfo())
+ unexpectedItemInfo2.user = UserHandle(10)
+ whenever(unexpectedItemInfo2.targetComponent).thenReturn(expectedComponentName)
+
+ val itemInfoStream =
+ listOf(expectedItemInfo, unexpectedItemInfo1, unexpectedItemInfo2).stream()
+
+ // When
+ val predicate =
+ ItemInfoMatcher.ofComponents(hashSetOf(expectedComponentName), expectedUserHandle)
+ val actualResults = itemInfoStream.filter(predicate).toList()
+
+ // Then
+ assertThat(actualResults).containsExactly(expectedItemInfo)
+ }
+
+ @Test
+ fun `ofPackages returns Predicate for ItemInfo containing UserHandle and target package`() {
+ // Given
+ val expectedUserHandle = UserHandle(0)
+ val expectedPackage = "expectedPackage"
+ val expectedComponentName = ComponentName(expectedPackage, "expectedClass")
+ val expectedItemInfo = spy(ItemInfo())
+ expectedItemInfo.user = expectedUserHandle
+ whenever(expectedItemInfo.targetComponent).thenReturn(expectedComponentName)
+
+ val unexpectedPackage = "unexpectedPackage"
+ val unexpectedComponentName = ComponentName(unexpectedPackage, "unexpectedClass")
+ val unexpectedItemInfo1 = spy(ItemInfo())
+ unexpectedItemInfo1.user = expectedUserHandle
+ whenever(unexpectedItemInfo1.targetComponent).thenReturn(unexpectedComponentName)
+
+ val unexpectedItemInfo2 = spy(ItemInfo())
+ unexpectedItemInfo2.user = UserHandle(10)
+ whenever(unexpectedItemInfo2.targetComponent).thenReturn(expectedComponentName)
+
+ val itemInfoStream =
+ listOf(expectedItemInfo, unexpectedItemInfo1, unexpectedItemInfo2).stream()
+
+ // When
+ val predicate = ItemInfoMatcher.ofPackages(setOf(expectedPackage), expectedUserHandle)
+ val actualResults = itemInfoStream.filter(predicate).toList()
+
+ // Then
+ assertThat(actualResults).containsExactly(expectedItemInfo)
+ }
+
+ @Test
+ fun `ofShortcutKeys returns Predicate for Deep Shortcut Info containing given ShortcutKey`() {
+ // Given
+ val expectedItemInfo = spy(ItemInfo())
+ expectedItemInfo.itemType = ITEM_TYPE_DEEP_SHORTCUT
+ val expectedIntent =
+ Intent().apply {
+ putExtra("shortcut_id", "expectedShortcut")
+ `package` = "expectedPackage"
+ }
+ whenever(expectedItemInfo.intent).thenReturn(expectedIntent)
+
+ val unexpectedIntent =
+ Intent().apply {
+ putExtra("shortcut_id", "unexpectedShortcut")
+ `package` = "unexpectedPackage"
+ }
+ val unexpectedItemInfo = spy(ItemInfo())
+ unexpectedItemInfo.itemType = ITEM_TYPE_DEEP_SHORTCUT
+ whenever(unexpectedItemInfo.intent).thenReturn(unexpectedIntent)
+
+ val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+ val expectedShortcutKey = ShortcutKey.fromItemInfo(expectedItemInfo)
+
+ // When
+ val predicate = ItemInfoMatcher.ofShortcutKeys(setOf(expectedShortcutKey))
+ val actualResults = itemInfoStream.filter(predicate).toList()
+
+ // Then
+ assertThat(actualResults).containsExactly(expectedItemInfo)
+ }
+
+ @Test
+ fun `forFolderMatch returns Predicate to match against children within Folder ItemInfo`() {
+ // Given
+ val expectedItemInfo = spy(FolderInfo())
+ expectedItemInfo.itemType = ITEM_TYPE_FOLDER
+ val expectedIntent =
+ Intent().apply {
+ putExtra("shortcut_id", "expectedShortcut")
+ `package` = "expectedPackage"
+ }
+ val expectedChildInfo = spy(ItemInfo())
+ expectedChildInfo.itemType = ITEM_TYPE_DEEP_SHORTCUT
+ whenever(expectedChildInfo.intent).thenReturn(expectedIntent)
+ whenever(expectedItemInfo.getContents()).thenReturn(arrayListOf(expectedChildInfo))
+
+ val unexpectedItemInfo = spy(FolderInfo())
+ unexpectedItemInfo.itemType = ITEM_TYPE_FOLDER
+
+ val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+ val expectedShortcutKey = ShortcutKey.fromItemInfo(expectedChildInfo)
+
+ // When
+ val predicate = ItemInfoMatcher.forFolderMatch(ofShortcutKeys(setOf(expectedShortcutKey)))
+ val actualResults = itemInfoStream.filter(predicate).toList()
+
+ // Then
+ assertThat(actualResults).containsExactly(expectedItemInfo)
+ }
+
+ @Test
+ fun `ofItemIds returns Predicate to match ItemInfo that contains given ids`() {
+ // Given
+ val expectedItemInfo = spy(ItemInfo())
+ expectedItemInfo.id = 1
+
+ val unexpectedItemInfo = spy(ItemInfo())
+ unexpectedItemInfo.id = 2
+
+ val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+
+ // When
+ val expectedIds = IntSet().apply { add(1) }
+ val predicate = ItemInfoMatcher.ofItemIds(expectedIds)
+ val actualResults = itemInfoStream.filter(predicate).toList()
+
+ // Then
+ assertThat(actualResults).containsExactly(expectedItemInfo)
+ }
+
+ @Test
+ fun `ofItems returns Predicate matching against provided ItemInfo`() {
+ // Given
+ val expectedItemInfo = spy(ItemInfo())
+ expectedItemInfo.id = 1
+
+ val unexpectedItemInfo = spy(ItemInfo())
+ unexpectedItemInfo.id = 2
+
+ val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+
+ // When
+ val expectedItems = setOf(expectedItemInfo)
+ val predicate = ItemInfoMatcher.ofItems(expectedItems)
+ val actualResults = itemInfoStream.filter(predicate).toList()
+
+ // Then
+ assertThat(actualResults).containsExactly(expectedItemInfo)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt
new file mode 100644
index 0000000..94bd7c9
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RunnableListTest {
+
+ @Mock private lateinit var runnable1: Runnable
+ @Mock private lateinit var runnable2: Runnable
+
+ private val underTest = RunnableList()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun not_destroyedByDefault() {
+ assertThat(underTest.isDestroyed).isFalse()
+ }
+
+ @Test
+ fun add_and_run() {
+ underTest.add(runnable1)
+ underTest.add(runnable2)
+
+ underTest.executeAllAndDestroy()
+
+ verify(runnable1).run()
+ verify(runnable2).run()
+ assertThat(underTest.isDestroyed).isTrue()
+ }
+
+ @Test
+ fun add_to_destroyed_runnableList_run_immediately() {
+ underTest.executeAllAndDestroy()
+
+ underTest.add(runnable1)
+
+ verify(runnable1).run()
+ }
+
+ @Test
+ fun second_executeAllAndDestroy_noOp() {
+ underTest.executeAllAndDestroy()
+ underTest.add(runnable1)
+ reset(runnable1)
+
+ underTest.executeAllAndDestroy()
+
+ verifyNoMoreInteractions(runnable1)
+ }
+
+ @Test
+ fun executeAllAndClear_run_not_destroy() {
+ underTest.add(runnable1)
+ underTest.add(runnable2)
+
+ underTest.executeAllAndClear()
+
+ verify(runnable1).run()
+ verify(runnable2).run()
+ assertThat(underTest.isDestroyed).isFalse()
+ }
+
+ @Test
+ fun executeAllAndClear_not_destroy() {
+ underTest.executeAllAndClear()
+ underTest.add(runnable1)
+ reset(runnable1)
+
+ underTest.executeAllAndClear()
+
+ verify(runnable1).run()
+ }
+
+ @Test
+ fun remove_and_run_not_executed() {
+ underTest.add(runnable1)
+ underTest.add(runnable2)
+
+ underTest.remove(runnable1)
+ underTest.executeAllAndClear()
+
+ verifyNoMoreInteractions(runnable1)
+ verify(runnable2).run()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
new file mode 100644
index 0000000..430aad2
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_SCREEN_OFF
+import android.content.Intent.ACTION_SCREEN_ON
+import android.content.Intent.ACTION_USER_PRESENT
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenOnTrackerTest {
+
+ @Mock private lateinit var receiver: SimpleBroadcastReceiver
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var listener: ScreenOnTracker.ScreenOnListener
+
+ private lateinit var underTest: ScreenOnTracker
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = ScreenOnTracker(context, receiver)
+ }
+
+ @Test
+ fun test_default_state() {
+ verify(receiver).register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT)
+ assertThat(underTest.isScreenOn).isTrue()
+ }
+
+ @Test
+ fun close_unregister_receiver() {
+ underTest.close()
+
+ verify(receiver).unregisterReceiverSafely(context)
+ }
+
+ @Test
+ fun add_listener_then_receive_screen_on_intent_notify_listener() {
+ underTest.addListener(listener)
+
+ underTest.onReceive(Intent(ACTION_SCREEN_ON))
+
+ verify(listener).onScreenOnChanged(true)
+ assertThat(underTest.isScreenOn).isTrue()
+ }
+
+ @Test
+ fun add_listener_then_receive_screen_off_intent_notify_listener() {
+ underTest.addListener(listener)
+
+ underTest.onReceive(Intent(ACTION_SCREEN_OFF))
+
+ verify(listener).onScreenOnChanged(false)
+ assertThat(underTest.isScreenOn).isFalse()
+ }
+
+ @Test
+ fun add_listener_then_receive_user_present_intent_notify_listener() {
+ underTest.addListener(listener)
+
+ underTest.onReceive(Intent(ACTION_USER_PRESENT))
+
+ verify(listener).onUserPresent()
+ assertThat(underTest.isScreenOn).isTrue()
+ }
+
+ @Test
+ fun remove_listener_then_receive_intent_noOp() {
+ underTest.addListener(listener)
+
+ underTest.removeListener(listener)
+
+ underTest.onReceive(Intent(ACTION_USER_PRESENT))
+ verifyNoMoreInteractions(listener)
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt
new file mode 100644
index 0000000..612fcd4
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.view.View
+import android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+import android.view.Window
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
+import com.android.launcher3.util.SystemUiController.UI_STATE_BASE_WINDOW
+import com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SystemUiControllerTest {
+
+ @Mock private lateinit var window: Window
+ @Mock private lateinit var decorView: View
+
+ private lateinit var underTest: SystemUiController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(window.decorView).thenReturn(decorView)
+ underTest = SystemUiController(window)
+ }
+
+ @Test
+ fun test_default_state() {
+ assertThat(underTest.toString()).isEqualTo("mStates=[0, 0, 0, 0, 0]")
+ }
+
+ @Test
+ fun update_state_base_window_light() {
+ underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ true)
+
+ val visibility =
+ View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ verify(decorView).systemUiVisibility = eq(visibility)
+ assertThat(underTest.baseSysuiVisibility).isEqualTo(visibility)
+ val flag = FLAG_LIGHT_NAV or FLAG_LIGHT_STATUS
+ assertThat(underTest.toString()).isEqualTo("mStates=[$flag, 0, 0, 0, 0]")
+ }
+
+ @Test
+ fun update_state_scrim_view_light() {
+ underTest.updateUiState(UI_STATE_SCRIM_VIEW, /* isLight= */ true)
+
+ verify(decorView).systemUiVisibility =
+ eq(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
+ assertThat(underTest.baseSysuiVisibility).isEqualTo(0)
+ val flag = FLAG_LIGHT_NAV or FLAG_LIGHT_STATUS
+ assertThat(underTest.toString()).isEqualTo("mStates=[0, $flag, 0, 0, 0]")
+ }
+
+ @Test
+ fun update_state_base_window_dark() {
+ underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ false)
+
+ verify(decorView, never()).systemUiVisibility = anyInt()
+ assertThat(underTest.baseSysuiVisibility).isEqualTo(0)
+ val flag = FLAG_DARK_NAV or FLAG_DARK_STATUS
+ assertThat(underTest.toString()).isEqualTo("mStates=[$flag, 0, 0, 0, 0]")
+ }
+
+ @Test
+ fun update_state_scrim_view_dark() {
+ underTest.updateUiState(UI_STATE_SCRIM_VIEW, /* isLight= */ false)
+
+ verify(decorView, never()).systemUiVisibility = anyInt()
+ assertThat(underTest.baseSysuiVisibility).isEqualTo(0)
+ val flag = FLAG_DARK_NAV or FLAG_DARK_STATUS
+ assertThat(underTest.toString()).isEqualTo("mStates=[0, $flag, 0, 0, 0]")
+ }
+
+ @Test
+ fun get_base_sysui_visibility() {
+ `when`(decorView.systemUiVisibility).thenReturn(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
+
+ assertThat(underTest.baseSysuiVisibility).isEqualTo(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
+ }
+
+ @Test
+ fun update_state_base_window_light_with_existing_visibility() {
+ `when`(decorView.systemUiVisibility).thenReturn(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
+
+ underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ true)
+
+ val visibility =
+ View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or
+ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or
+ SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ assertThat(underTest.baseSysuiVisibility).isEqualTo(visibility)
+ verify(decorView).systemUiVisibility = eq(visibility)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt
new file mode 100644
index 0000000..d82818d
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.R
+import com.android.launcher3.util.ViewCache.CacheEntry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ViewCacheTest {
+
+ private lateinit var underTest: ViewCache
+
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val layoutId =
+ context.run { resources.getIdentifier("test_layout_appwidget_blue", "layout", packageName) }
+
+ @Before
+ fun setUp() {
+ underTest = ViewCache()
+ underTest.setCacheSize(layoutId, 5)
+ }
+
+ @Test
+ fun get_view_from_empty_cache() {
+ val view: View = underTest.getView(layoutId, context, null)
+
+ val cacheEntry = view.getTag(R.id.cache_entry_tag_id) as ViewCache.CacheEntry
+ assertThat(cacheEntry).isNotNull()
+ assertThat(cacheEntry.mMaxSize).isEqualTo(5)
+ assertThat(cacheEntry.mCurrentSize).isEqualTo(0)
+ assertThat(cacheEntry.mViews[0]).isNull()
+ }
+
+ @Test
+ fun recyclerView() {
+ val view: View = underTest.getView(layoutId, context, null)
+ val cacheEntry = view.getTag(R.id.cache_entry_tag_id) as ViewCache.CacheEntry
+
+ underTest.recycleView(layoutId, view)
+
+ assertThat(cacheEntry.mMaxSize).isEqualTo(5)
+ assertThat(cacheEntry.mCurrentSize).isEqualTo(1)
+ assertThat(cacheEntry.mViews[0]).isSameInstanceAs(view)
+ }
+
+ @Test
+ fun get_view_from_cache() {
+ val view: View = underTest.getView(layoutId, context, null)
+ underTest.recycleView(layoutId, view)
+
+ val newView = underTest.getView<View>(layoutId, context, null)
+
+ assertThat(view).isSameInstanceAs(newView)
+ }
+
+ @Test
+ fun change_tag_id_recyclerView_noOp() {
+ val view: View = underTest.getView(layoutId, context, null)
+ val cacheEntry = view.getTag(R.id.cache_entry_tag_id) as ViewCache.CacheEntry
+
+ view.setTag(R.id.cache_entry_tag_id, CacheEntry(3))
+ underTest.recycleView(layoutId, view)
+
+ assertThat(cacheEntry.mMaxSize).isEqualTo(5)
+ assertThat(cacheEntry.mCurrentSize).isEqualTo(0)
+ assertThat(cacheEntry.mViews[0]).isNull()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt
new file mode 100644
index 0000000..e535656
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.annotation.UiThreadTest
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.same
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit::class)
+@UiThreadTest
+class ViewPoolTest {
+
+ @Mock private lateinit var viewParent: ViewGroup
+ @Mock private lateinit var view: ReusableView
+ @Mock private lateinit var inflater: LayoutInflater
+
+ private lateinit var underTest: ViewPool<ReusableView>
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(inflater.cloneInContext(any())).thenReturn(inflater)
+ underTest = ViewPool(inflater, viewParent, LAYOUT_ID, 10, 0)
+ }
+
+ @Test
+ fun get_view_from_empty_pool_inflate_new_view() {
+ underTest.view
+
+ verify(inflater).inflate(eq(LAYOUT_ID), same(viewParent), eq(false))
+ }
+
+ @Test
+ fun recycle_view() {
+ underTest.recycle(view)
+
+ val returnedView = underTest.view
+
+ verify(view).onRecycle()
+ assertThat(returnedView).isSameInstanceAs(view)
+ verifyNoMoreInteractions(inflater)
+ }
+
+ @Test
+ fun get_view_twice_from_view_pool_with_one_view() {
+ underTest.recycle(view)
+ underTest.view
+ verifyNoMoreInteractions(inflater)
+
+ underTest.view
+
+ verify(inflater).inflate(eq(LAYOUT_ID), same(viewParent), eq(false))
+ }
+
+ companion object {
+ private const val LAYOUT_ID = 1000
+ }
+
+ private inner class ReusableView(context: Context) : View(context), ViewPool.Reusable {
+ override fun onRecycle() {
+ TODO("Not yet implemented")
+ }
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt
new file mode 100644
index 0000000..1a659e2
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget
+
+import androidx.test.annotation.UiThreadTest
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.BuildConfig.WIDGETS_ENABLED
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_ACTIVITY_RESUMED
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_ACTIVITY_STARTED
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_STATE_IS_NORMAL
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit::class)
+class LauncherWidgetHolderTest {
+ private lateinit var widgetHolder: LauncherWidgetHolder
+
+ @Before
+ fun setUp() {
+ assertTrue(WIDGETS_ENABLED)
+ widgetHolder =
+ LauncherWidgetHolder(ActivityContextWrapper(getInstrumentation().targetContext)) {}
+ }
+
+ @After
+ fun tearDown() {
+ widgetHolder.destroy()
+ }
+
+ @Test
+ fun widget_holder_start_listening() {
+ val testView = mock(PendingAppWidgetHostView::class.java)
+ widgetHolder.mViews[0] = testView
+ widgetHolder.setListeningFlag(false)
+ assertFalse(widgetHolder.isListening)
+ widgetHolder.startListening()
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ getInstrumentation().waitForIdleSync()
+ assertTrue(widgetHolder.isListening)
+ verify(testView, times(1)).reInflate()
+ widgetHolder.clearWidgetViews()
+ }
+
+ @Test
+ fun holder_start_listening_after_activity_start() {
+ widgetHolder.setShouldListenFlag(FLAG_STATE_IS_NORMAL or FLAG_ACTIVITY_RESUMED, true)
+ widgetHolder.setActivityStarted(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setActivityStarted(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ fun holder_start_listening_after_activity_resume() {
+ widgetHolder.setShouldListenFlag(FLAG_STATE_IS_NORMAL or FLAG_ACTIVITY_STARTED, true)
+ widgetHolder.setActivityResumed(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setActivityResumed(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ fun holder_start_listening_after_state_normal() {
+ widgetHolder.setShouldListenFlag(FLAG_ACTIVITY_RESUMED or FLAG_ACTIVITY_STARTED, true)
+ widgetHolder.setStateIsNormal(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setStateIsNormal(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ @UiThreadTest
+ fun widget_holder_create_view() {
+ val mockProviderInfo = mock(LauncherAppWidgetProviderInfo::class.java)
+ doReturn(false).whenever(mockProviderInfo).isCustomWidget
+ assertEquals(0, widgetHolder.mViews.size())
+ widgetHolder.createView(APP_WIDGET_ID, mockProviderInfo)
+ assertEquals(1, widgetHolder.mViews.size())
+ assertEquals(APP_WIDGET_ID, widgetHolder.mViews.get(0).appWidgetId)
+ widgetHolder.deleteAppWidgetId(APP_WIDGET_ID)
+ assertEquals(0, widgetHolder.mViews.size())
+ }
+
+ @Test
+ fun holder_add_provider_change_listener() {
+ val listener = LauncherWidgetHolder.ProviderChangedListener {}
+ widgetHolder.addProviderChangeListener(listener)
+ getInstrumentation().waitForIdleSync()
+ assertEquals(1, widgetHolder.mProviderChangedListeners.size)
+ assertSame(widgetHolder.mProviderChangedListeners.first(), listener)
+ widgetHolder.removeProviderChangeListener(listener)
+ }
+
+ @Test
+ fun holder_remove_provider_change_listener() {
+ val listener = LauncherWidgetHolder.ProviderChangedListener {}
+ widgetHolder.addProviderChangeListener(listener)
+ widgetHolder.removeProviderChangeListener(listener)
+ getInstrumentation().waitForIdleSync()
+ assertEquals(0, widgetHolder.mProviderChangedListeners.size)
+ }
+
+ @Test
+ fun widget_holder_stop_listening() {
+ widgetHolder.setListeningFlag(true)
+ assertTrue(widgetHolder.isListening)
+ widgetHolder.stopListening()
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.isListening)
+ }
+
+ companion object {
+ private const val APP_WIDGET_ID = 0
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/ListenableHostViewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/ListenableHostViewTest.kt
new file mode 100644
index 0000000..6c71f36
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/ListenableHostViewTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget
+
+import android.content.Context
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListenableHostViewTest {
+
+ private val context: Context
+ get() = ActivityContextWrapper(InstrumentationRegistry.getInstrumentation().targetContext)
+
+ @Test
+ fun updateAppWidget_notifiesListeners() {
+ val hostView = ListenableHostView(context)
+ var wasNotifiedOfUpdate = false
+ val updateListener = Runnable { wasNotifiedOfUpdate = true }
+ hostView.addUpdateListener(updateListener)
+ hostView.beginDeferringUpdates()
+ hostView.updateAppWidget(null)
+ Truth.assertThat(wasNotifiedOfUpdate).isTrue()
+ }
+
+ @Test
+ fun onInitializeAccessibilityNodeInfo_correctlySetsClassName() {
+ val hostView = ListenableHostView(context)
+ val nodeInfo = AccessibilityNodeInfo()
+ hostView.onInitializeAccessibilityNodeInfo(nodeInfo)
+ Truth.assertThat(nodeInfo.className).isEqualTo(LauncherAppWidgetHostView::class.java.name)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt
new file mode 100644
index 0000000..db77702
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.R
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RoundedCornerEnforcementTest {
+
+ @Test
+ fun `Widget view has one background`() {
+ val mockWidgetView = mock(LauncherAppWidgetHostView::class.java)
+
+ doReturn(android.R.id.background).whenever(mockWidgetView).id
+
+ assertSame(RoundedCornerEnforcement.findBackground(mockWidgetView), mockWidgetView)
+ }
+
+ @Test
+ fun `Widget opted out of rounded corner enforcement`() {
+ val mockView = mock(View::class.java)
+
+ doReturn(android.R.id.background).whenever(mockView).id
+ doReturn(true).whenever(mockView).clipToOutline
+
+ assertTrue(RoundedCornerEnforcement.hasAppWidgetOptedOut(mockView))
+ }
+
+ @Test
+ fun `Compute rect based on widget view with background`() {
+ val mockBackgroundView = mock(View::class.java)
+ val mockWidgetView = mock(ViewGroup::class.java)
+ val testRect = Rect(0, 0, 0, 0)
+
+ doReturn(WIDTH).whenever(mockBackgroundView).width
+ doReturn(HEIGHT).whenever(mockBackgroundView).height
+ doReturn(LEFT).whenever(mockBackgroundView).left
+ doReturn(TOP).whenever(mockBackgroundView).top
+ doReturn(mockWidgetView).whenever(mockBackgroundView).parent
+
+ RoundedCornerEnforcement.computeRoundedRectangle(
+ mockWidgetView,
+ mockBackgroundView,
+ testRect
+ )
+
+ assertEquals(Rect(50, 75, 250, 275), testRect)
+ }
+
+ @Test
+ fun `Compute system radius`() {
+ val mockContext = mock(Context::class.java)
+ val mockRes = mock(Resources::class.java)
+
+ doReturn(mockRes).whenever(mockContext).resources
+ doReturn(RADIUS)
+ .whenever(mockRes)
+ .getDimension(eq(android.R.dimen.system_app_widget_background_radius))
+ doReturn(LAUNCHER_RADIUS)
+ .whenever(mockRes)
+ .getDimension(eq(R.dimen.enforced_rounded_corner_max_radius))
+
+ assertEquals(RADIUS, RoundedCornerEnforcement.computeEnforcedRadius(mockContext))
+ }
+
+ companion object {
+ const val WIDTH = 200
+ const val HEIGHT = 200
+ const val LEFT = 50
+ const val TOP = 75
+
+ const val RADIUS = 8f
+ const val LAUNCHER_RADIUS = 16f
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/WidgetAddFlowHandlerTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetAddFlowHandlerTest.kt
new file mode 100644
index 0000000..242ec7c
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetAddFlowHandlerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget
+
+import android.content.Context
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Launcher
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetAddFlowHandlerTest {
+
+ private val context: Context
+ get() = ActivityContextWrapper(InstrumentationRegistry.getInstrumentation().targetContext)
+
+ private val providerInfo =
+ LauncherAppWidgetProviderInfo().apply {
+ configure = InstrumentationRegistry.getInstrumentation().componentName
+ }
+ private val appWidgetHolder: LauncherWidgetHolder = mock<LauncherWidgetHolder>()
+ private val launcher: Launcher =
+ mock<Launcher>().also { whenever(it.appWidgetHolder).thenReturn(appWidgetHolder) }
+ private val appWidgetInfo = LauncherAppWidgetInfo().apply { appWidgetId = 123 }
+ private val requestCode = 123
+ private val flowHandler = WidgetAddFlowHandler(providerInfo)
+
+ @Test
+ fun valuesShouldRemainTheSame_beforeAndAfter_parcelization() {
+ with(Bundle()) {
+ val testKey = "testKey"
+ putParcelable(testKey, flowHandler)
+ Truth.assertThat(getParcelable(testKey, WidgetAddFlowHandler::class.java))
+ .isEqualTo(flowHandler)
+ }
+ }
+
+ @Test
+ fun describeContents_shouldReturn_0() {
+ Truth.assertThat(flowHandler.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun startBindFlow_shouldCorrectly_startLauncherFlowBinding() {
+ flowHandler.startBindFlow(launcher, appWidgetInfo.appWidgetId, appWidgetInfo, requestCode)
+ verify(launcher).setWaitingForResult(any())
+ verify(appWidgetHolder)
+ .startBindFlow(launcher, appWidgetInfo.appWidgetId, providerInfo, requestCode)
+ }
+
+ @Test
+ fun startConfigActivityWithCustomAppWidgetId_shouldAskLauncherToStartConfigActivity() {
+ flowHandler.startConfigActivity(
+ launcher,
+ appWidgetInfo.appWidgetId,
+ ItemInfo(),
+ requestCode
+ )
+ verify(launcher).setWaitingForResult(any())
+ verify(appWidgetHolder)
+ .startConfigActivity(launcher, appWidgetInfo.appWidgetId, requestCode)
+ }
+
+ @Test
+ fun startConfigActivity_shouldAskLauncherToStartConfigActivity() {
+ flowHandler.startConfigActivity(launcher, appWidgetInfo, requestCode)
+ verify(launcher).setWaitingForResult(any())
+ verify(appWidgetHolder)
+ .startConfigActivity(launcher, appWidgetInfo.appWidgetId, requestCode)
+ }
+
+ @Test
+ fun needsConfigure_returnsTrue_ifFlagsAndProviderInfoDetermineSo() {
+ Truth.assertThat(flowHandler.needsConfigure()).isTrue()
+ }
+
+ @Test
+ fun getProviderInfo_returnCorrectProviderInfo() {
+ Truth.assertThat(flowHandler.getProviderInfo(context)).isSameInstanceAs(providerInfo)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/WidgetManagerHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetManagerHelperTest.kt
new file mode 100644
index 0000000..f1cfb79
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetManagerHelperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget
+
+import android.appwidget.AppWidgetManager
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.os.Process
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.PackageUserKey
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetManagerHelperTest {
+
+ private val context: Context
+ get() = ActivityContextWrapper(InstrumentationRegistry.getInstrumentation().targetContext)
+
+ private val info =
+ LauncherAppWidgetProviderInfo().apply {
+ provider = InstrumentationRegistry.getInstrumentation().componentName
+ providerInfo =
+ mock(ActivityInfo::class.java).apply { applicationInfo = context.applicationInfo }
+ }
+
+ @Mock private lateinit var appWidgetManager: AppWidgetManager
+
+ private lateinit var underTest: WidgetManagerHelper
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = WidgetManagerHelper(context, appWidgetManager)
+ }
+
+ @Test
+ fun getAllProviders_returnsCorrectWidgetProviderInfo() {
+ val packageUserKey =
+ mock(PackageUserKey::class.java).apply {
+ mPackageName = context.packageName
+ mUser = Process.myUserHandle()
+ }
+ val desiredResult = listOf(info)
+ whenever(
+ appWidgetManager.getInstalledProvidersForPackage(
+ packageUserKey.mPackageName,
+ packageUserKey.mUser
+ )
+ )
+ .thenReturn(desiredResult)
+ Truth.assertThat(underTest.getAllProviders(packageUserKey)).isSameInstanceAs(desiredResult)
+ }
+
+ @Test
+ fun getLauncherAppWidgetInfo_returnsCorrectInfo_ifWidgetExists() {
+ val id = 123
+ whenever(appWidgetManager.getAppWidgetInfo(id)).thenReturn(info)
+ val componentName = InstrumentationRegistry.getInstrumentation().componentName
+ Truth.assertThat(underTest.getLauncherAppWidgetInfo(id, componentName))
+ .isSameInstanceAs(info)
+ }
+
+ @Test
+ fun bindAppWidgetIdIfAllowed_correctly_forwardsBindCommandToAppWidgetManager() {
+ val id = 124
+ val options = Bundle()
+ underTest.bindAppWidgetIdIfAllowed(id, info, options)
+ verify(appWidgetManager).bindAppWidgetIdIfAllowed(id, info.profile, info.provider, options)
+ }
+
+ @Test
+ fun findProvider_returnsNull_ifNoProviderExists() {
+ val info =
+ underTest.getLauncherAppWidgetInfo(
+ 1,
+ InstrumentationRegistry.getInstrumentation().componentName
+ )
+ Truth.assertThat(info).isNull()
+ }
+
+ @Test
+ fun isAppWidgetRestored_returnsTrue_ifWidgetIsRestored() {
+ val id = 126
+ whenever(appWidgetManager.getAppWidgetOptions(id))
+ .thenReturn(
+ Bundle().apply {
+ putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true)
+ }
+ )
+ Truth.assertThat(underTest.isAppWidgetRestored(id)).isTrue()
+ }
+
+ @Test
+ fun loadGeneratedPreview_returnsWidgetPreview_fromAppWidgetManager() {
+ val widgetCategory = 130
+ with(info) {
+ underTest.loadGeneratedPreview(this, widgetCategory)
+ verify(appWidgetManager).getWidgetPreview(provider, profile, widgetCategory)
+ }
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt
new file mode 100644
index 0000000..0a3035a
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.custom
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class CustomAppWidgetProviderInfoTest {
+
+ private lateinit var underTest: CustomAppWidgetProviderInfo
+
+ @Before
+ fun setup() {
+ underTest = CustomAppWidgetProviderInfo()
+ underTest.provider = PROVIDER_NAME
+ }
+
+ @Test
+ fun info_to_string() {
+ assertEquals("WidgetProviderInfo($PROVIDER_NAME)", underTest.toString())
+ }
+
+ @Test
+ fun get_label() {
+ underTest.label = " TEST_LABEL"
+ assertEquals(LABEL_NAME, underTest.getLabel(mock(PackageManager::class.java)))
+ }
+
+ companion object {
+ private val PROVIDER_NAME =
+ ComponentName(getInstrumentation().targetContext.packageName, "TEST_PACKAGE")
+ private const val LABEL_NAME = "TEST_LABEL"
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt
new file mode 100644
index 0000000..4b5710d
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.custom
+
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.os.Process
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.PluginManagerWrapper
+import com.android.launcher3.util.WidgetUtils
+import com.android.launcher3.widget.LauncherAppWidgetHostView
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.android.systemui.plugins.CustomWidgetPlugin
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomWidgetManagerTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val context = SandboxModelContext()
+ private lateinit var underTest: CustomWidgetManager
+
+ @Mock private lateinit var pluginManager: PluginManagerWrapper
+ @Mock private lateinit var mockAppWidgetManager: AppWidgetManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.putObject(PluginManagerWrapper.INSTANCE, pluginManager)
+ underTest = CustomWidgetManager(context, mockAppWidgetManager)
+ }
+
+ @After
+ fun tearDown() {
+ underTest.close()
+ }
+
+ @Test
+ fun plugin_manager_added_after_initialization() {
+ verify(pluginManager)
+ .addPluginListener(same(underTest), same(CustomWidgetPlugin::class.java), eq(true))
+ }
+
+ @Test
+ fun close_widget_manager_should_remove_plugin_listener() {
+ underTest.close()
+ verify(pluginManager).removePluginListener(same(underTest))
+ }
+
+ @Test
+ fun on_plugin_connected_no_provider_info() {
+ doReturn(emptyList<LauncherAppWidgetProviderInfo>())
+ .whenever(mockAppWidgetManager)
+ .getInstalledProvidersForProfile(any())
+ val mockPlugin = mock(CustomWidgetPlugin::class.java)
+ underTest.onPluginConnected(mockPlugin, context)
+ assertEquals(0, underTest.plugins.size)
+ }
+
+ @Test
+ fun on_plugin_connected_exist_provider_info() {
+ doReturn(listOf(WidgetUtils.createAppWidgetProviderInfo(TEST_COMPONENT_NAME)))
+ .whenever(mockAppWidgetManager)
+ .getInstalledProvidersForProfile(eq(Process.myUserHandle()))
+ val mockPlugin = mock(CustomWidgetPlugin::class.java)
+ underTest.onPluginConnected(mockPlugin, context)
+ assertEquals(1, underTest.plugins.size)
+ }
+
+ @Test
+ fun on_plugin_disconnected() {
+ doReturn(listOf(WidgetUtils.createAppWidgetProviderInfo(TEST_COMPONENT_NAME)))
+ .whenever(mockAppWidgetManager)
+ .getInstalledProvidersForProfile(eq(Process.myUserHandle()))
+ val mockPlugin = mock(CustomWidgetPlugin::class.java)
+ underTest.onPluginConnected(mockPlugin, context)
+ underTest.onPluginDisconnected(mockPlugin)
+ assertEquals(0, underTest.plugins.size)
+ }
+
+ @Test
+ fun on_view_created() {
+ val mockPlugin = mock(CustomWidgetPlugin::class.java)
+ val mockWidgetView = mock(LauncherAppWidgetHostView::class.java)
+ val mockProviderInfo = mock(CustomAppWidgetProviderInfo::class.java)
+ doReturn(mockProviderInfo).whenever(mockWidgetView).appWidgetInfo
+ mockProviderInfo.provider = TEST_COMPONENT_NAME
+ underTest.plugins.put(TEST_COMPONENT_NAME, mockPlugin)
+ underTest.onViewCreated(mockWidgetView)
+ verify(mockPlugin).onViewCreated(eq(mockWidgetView))
+ }
+
+ @Test
+ fun generate_stream() {
+ assertTrue(underTest.stream().toList().isEmpty())
+ doReturn(listOf(WidgetUtils.createAppWidgetProviderInfo(TEST_COMPONENT_NAME)))
+ .whenever(mockAppWidgetManager)
+ .getInstalledProvidersForProfile(eq(Process.myUserHandle()))
+ val mockPlugin = mock(CustomWidgetPlugin::class.java)
+ underTest.onPluginConnected(mockPlugin, context)
+ assertEquals(1, underTest.stream().toList().size)
+ }
+
+ companion object {
+ private const val TEST_CLASS = "TEST_CLASS"
+ private val TEST_COMPONENT_NAME =
+ ComponentName(getInstrumentation().targetContext.packageName, TEST_CLASS)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt
new file mode 100644
index 0000000..5df7caa
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.model
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.UserHandle
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.LimitDevicesRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.ComponentWithLabel
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.data.PackageItemInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.WidgetUtils
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+
+@RunWith(AndroidJUnit4::class)
+@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC])
+class WidgetsListBaseEntriesBuilderTest {
+ @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+ @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var iconCache: IconCache
+
+ private lateinit var userHandle: UserHandle
+ private lateinit var context: Context
+ private lateinit var testInvariantProfile: InvariantDeviceProfile
+ private lateinit var allWidgets: Map<PackageItemInfo, List<WidgetItem>>
+ private lateinit var underTest: WidgetsListBaseEntriesBuilder
+
+ @Before
+ fun setUp() {
+ userHandle = UserHandle.CURRENT
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ testInvariantProfile = LauncherAppState.getIDP(context)
+
+ doAnswer { invocation: InvocationOnMock ->
+ val componentWithLabel = invocation.getArgument<Any>(0) as ComponentWithLabel
+ componentWithLabel.getComponent().shortClassName
+ }
+ .`when`(iconCache)
+ .getTitleNoCache(any<ComponentWithLabel>())
+ underTest = WidgetsListBaseEntriesBuilder(context)
+
+ allWidgets =
+ mapOf(
+ // app 1
+ packageItemInfoWithTitle(APP_1_PACKAGE_NAME, APP_1_PACKAGE_TITLE) to
+ listOf(
+ createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_1_CLASS_NAME),
+ createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_2_CLASS_NAME)
+ ),
+ // app 2
+ packageItemInfoWithTitle(APP_2_PACKAGE_NAME, APP_2_PACKAGE_TITLE) to
+ listOf(createWidgetItem(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)),
+ // app 3
+ packageItemInfoWithTitle(APP_3_PACKAGE_NAME, APP_3_PACKAGE_TITLE) to
+ listOf(createWidgetItem(APP_3_PACKAGE_NAME, APP_3_PROVIDER_1_CLASS_NAME))
+ )
+ }
+
+ @Test
+ fun widgetsListBaseEntriesBuilder_addsHeaderAndContentEntries_withCorrectSectionName() {
+ val expectedWidgetsCountBySection =
+ listOf(
+ APP_1_EXPECTED_SECTION_NAME to 2,
+ APP_2_EXPECTED_SECTION_NAME to 1,
+ APP_3_EXPECTED_SECTION_NAME to 1
+ )
+
+ val entries = underTest.build(allWidgets)
+
+ assertThat(entries).hasSize(6)
+ val headerEntrySectionAndWidgetSizes =
+ entries.filterIsInstance<WidgetsListHeaderEntry>().map {
+ it.mTitleSectionName to it.mWidgets.size
+ }
+ val contentEntrySectionAndWidgetSizes =
+ entries.filterIsInstance<WidgetsListContentEntry>().map {
+ it.mTitleSectionName to it.mWidgets.size
+ }
+ assertThat(headerEntrySectionAndWidgetSizes)
+ .containsExactlyElementsIn(expectedWidgetsCountBySection)
+ assertThat(contentEntrySectionAndWidgetSizes)
+ .containsExactlyElementsIn(expectedWidgetsCountBySection)
+ }
+
+ @Test
+ fun widgetsListBaseEntriesBuilder_withFilter_addsFilteredHeaderAndContentEntries() {
+ val allowList = listOf(APP_1_PROVIDER_1_CLASS_NAME, APP_3_PROVIDER_1_CLASS_NAME)
+ val expectedWidgetsCountBySection =
+ listOf(
+ APP_1_EXPECTED_SECTION_NAME to 1, // one widget filtered out
+ APP_3_EXPECTED_SECTION_NAME to 1
+ )
+
+ val entries =
+ underTest.build(allWidgets) { w -> allowList.contains(w.componentName.shortClassName) }
+
+ assertThat(entries).hasSize(4) // app 2 filtered out
+ val headerEntrySectionAndWidgetSizes =
+ entries.filterIsInstance<WidgetsListHeaderEntry>().map {
+ it.mTitleSectionName to it.mWidgets.size
+ }
+ val contentEntrySectionAndWidgetSizes =
+ entries.filterIsInstance<WidgetsListContentEntry>().map {
+ it.mTitleSectionName to it.mWidgets.size
+ }
+ assertThat(headerEntrySectionAndWidgetSizes)
+ .containsExactlyElementsIn(expectedWidgetsCountBySection)
+ assertThat(contentEntrySectionAndWidgetSizes)
+ .containsExactlyElementsIn(expectedWidgetsCountBySection)
+ }
+
+ private fun packageItemInfoWithTitle(packageName: String, title: String): PackageItemInfo {
+ val packageItemInfo = PackageItemInfo(packageName, userHandle)
+ packageItemInfo.title = title
+ return packageItemInfo
+ }
+
+ private fun createWidgetItem(packageName: String, widgetProviderName: String): WidgetItem {
+ val providerInfo =
+ WidgetUtils.createAppWidgetProviderInfo(
+ ComponentName.createRelative(packageName, widgetProviderName)
+ )
+ val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
+ return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
+ }
+
+ companion object {
+ const val APP_1_PACKAGE_NAME = "com.example.app1"
+ const val APP_1_PACKAGE_TITLE = "App1"
+ const val APP_1_EXPECTED_SECTION_NAME = "A" // for fast popup
+ const val APP_1_PROVIDER_1_CLASS_NAME = "app1Provider1"
+ const val APP_1_PROVIDER_2_CLASS_NAME = "app1Provider2"
+
+ const val APP_2_PACKAGE_NAME = "com.example.app2"
+ const val APP_2_PACKAGE_TITLE = "SomeApp2"
+ const val APP_2_EXPECTED_SECTION_NAME = "S" // for fast popup
+ const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1"
+
+ const val APP_3_PACKAGE_NAME = "com.example.app3"
+ const val APP_3_PACKAGE_TITLE = "OtherApp3"
+ const val APP_3_EXPECTED_SECTION_NAME = "O" // for fast popup
+ const val APP_3_PROVIDER_1_CLASS_NAME = "app3Provider1"
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
index 0370a6b..24d66a3 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -45,12 +45,12 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.search.SearchCallback;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
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.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
import org.junit.Before;
import org.junit.Test;
@@ -79,7 +79,7 @@
private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm;
@Mock
- private PopupDataProvider mDataProvider;
+ private WidgetsSearchDataProvider mDataProvider;
@Mock
private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
@@ -106,7 +106,7 @@
mSimpleWidgetsSearchAlgorithm = MAIN_EXECUTOR.submit(
() -> new SimpleWidgetsSearchAlgorithm(mDataProvider)).get();
- doReturn(Collections.EMPTY_LIST).when(mDataProvider).getAllWidgets();
+ doReturn(Collections.EMPTY_LIST).when(mDataProvider).getWidgets();
}
@Test
@@ -114,7 +114,7 @@
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
.when(mDataProvider)
- .getAllWidgets();
+ .getWidgets();
assertEquals(List.of(
WidgetsListHeaderEntry.createForSearch(
@@ -135,7 +135,7 @@
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
mCameraContentEntry))
.when(mDataProvider)
- .getAllWidgets();
+ .getWidgets();
assertEquals(List.of(
WidgetsListHeaderEntry.createForSearch(
@@ -162,7 +162,7 @@
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
.when(mDataProvider)
- .getAllWidgets();
+ .getWidgets();
mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback);
getInstrumentation().waitForIdleSync();
verify(mSearchCallback).onSearchResult(
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
index eac7f63..de48432 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -116,7 +116,8 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(getApplicationContext());
+ mContext = new ActivityContextWrapper(getApplicationContext(),
+ R.style.DynamicColorsBaseLauncherTheme);
when(mAllApps.getContext()).thenReturn(mContext);
when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
when(mUserCache.getUserProfiles())
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 41abcf8..76c1948 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -65,6 +65,7 @@
@Test
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
+ @ScreenRecordRule.ScreenRecord // b/353600888
public void testDragToFolder() {
// TODO: add the use case to drag an icon to an existing folder. Currently it either fails
// on tablets or phones due to difference in resolution.
@@ -97,6 +98,7 @@
* icon left.
*/
@Test
+ @ScreenRecordRule.ScreenRecord // b/353600888
public void testDragOutOfFolder() {
final HomeAppIcon playStoreIcon = createShortcutIfNotExist(STORE_APP_NAME, 0, 1);
final HomeAppIcon photosIcon = createShortcutInCenterIfNotExist(PHOTOS_APP_NAME);
@@ -174,13 +176,13 @@
public void testDragAndCancelAppIcon() {
final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
Point positionBeforeDrag =
- mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+ mLauncher.getWorkspace().getWorkspaceIconPosition(GMAIL_APP_NAME);
assertNotNull("App not found in Workspace before dragging.", positionBeforeDrag);
mLauncher.getWorkspace().dragAndCancelAppIcon(homeAppIcon);
Point positionAfterDrag =
- mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+ mLauncher.getWorkspace().getWorkspaceIconPosition(GMAIL_APP_NAME);
assertNotNull("App not found in Workspace after dragging.", positionAfterDrag);
assertEquals("App not returned to same position in Workspace after drag & cancel",
positionBeforeDrag, positionAfterDrag);
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 46cafa7..907aa50 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -46,7 +46,6 @@
import java.io.IOException;
import java.util.Arrays;
-import java.util.Map;
/**
* Test runs in Out of process (Oop) and In process (Ipc)
@@ -60,6 +59,7 @@
*/
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/349439239
public void testDeleteFromWorkspace() {
for (String appName : new String[]{GMAIL_APP_NAME, STORE_APP_NAME, TEST_APP_NAME}) {
final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
@@ -155,18 +155,14 @@
createShortcutIfNotExist(appNames[i], gridPositions[i]);
}
- Map<String, Point> initialPositions =
- mLauncher.getWorkspace().getWorkspaceIconsPositions();
- assertThat(initialPositions.keySet()).containsAtLeastElementsIn(appNames);
+ Point initialPosition =
+ mLauncher.getWorkspace().getWorkspaceIconPosition(DUMMY_APP_NAME);
+ assertThat(initialPosition).isNotNull();
final Workspace workspace = mLauncher.getWorkspace().getWorkspaceAppIcon(
DUMMY_APP_NAME).uninstall();
workspace.verifyWorkspaceAppIconIsGone(
DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
-
- Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
- Map<String, Point> finalPositions = workspace.getWorkspaceIconsPositions();
- assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
} finally {
TestUtil.uninstallDummyApp();
}
diff --git a/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt b/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt
new file mode 100644
index 0000000..e8681dc
--- /dev/null
+++ b/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.folder
+
+import android.graphics.Point
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+data class TestCase(val maxCountX: Int, val maxCountY: Int, val totalItems: Int)
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FolderPagedViewTest {
+
+ companion object {
+ private fun makeFolderGridOrganizer(testCase: TestCase): FolderGridOrganizer {
+ val folderGridOrganizer = FolderGridOrganizer(testCase.maxCountX, testCase.maxCountY)
+ folderGridOrganizer.setContentSize(testCase.totalItems)
+ return folderGridOrganizer
+ }
+ }
+
+ @Test
+ fun setContentSize() {
+ assertCountXandY(
+ TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+ expectedCountX = 4,
+ expectedCountY = 3
+ )
+ assertCountXandY(
+ TestCase(maxCountX = 4, maxCountY = 3, totalItems = 8),
+ expectedCountX = 3,
+ expectedCountY = 3
+ )
+ assertCountXandY(
+ TestCase(maxCountX = 4, maxCountY = 3, totalItems = 3),
+ expectedCountX = 2,
+ expectedCountY = 2
+ )
+ }
+
+ private fun assertCountXandY(testCase: TestCase, expectedCountX: Int, expectedCountY: Int) {
+ val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+ assert(folderGridOrganizer.countX == expectedCountX) {
+ "Error on expected countX $expectedCountX got ${folderGridOrganizer.countX} using test case $testCase"
+ }
+ assert(folderGridOrganizer.countY == expectedCountY) {
+ "Error on expected countY $expectedCountY got ${folderGridOrganizer.countY} using test case $testCase"
+ }
+ }
+
+ @Test
+ fun getPosForRank() {
+ assertFolderRank(
+ TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+ expectedPos = Point(0, 0),
+ rank = 0
+ )
+ assertFolderRank(
+ TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+ expectedPos = Point(1, 0),
+ rank = 1
+ )
+ assertFolderRank(
+ TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+ expectedPos = Point(3, 0),
+ rank = 3
+ )
+ assertFolderRank(
+ TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+ expectedPos = Point(2, 1),
+ rank = 6
+ )
+ val testCase = TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22)
+ // Rank 16 and 38 should yield the same point since 38 % 12 == 2
+ val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+ assertFolderRank(testCase, expectedPos = folderGridOrganizer.getPosForRank(2), rank = 38)
+ }
+
+ private fun assertFolderRank(testCase: TestCase, expectedPos: Point, rank: Int) {
+ val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+ val pos = folderGridOrganizer.getPosForRank(rank)
+ assert(pos == expectedPos) {
+ "Expected pos = $expectedPos doesn't match pos = $pos for the given rank $rank and the give test case $testCase"
+ }
+ }
+
+ @Test
+ fun isItemInPreview() {
+ val folderGridOrganizer = FolderGridOrganizer(5, 8)
+ folderGridOrganizer.setContentSize(ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1)
+ // Very few items
+ for (i in 0..3) {
+ assertItemsInPreview(
+ TestCase(
+ maxCountX = 5,
+ maxCountY = 8,
+ totalItems = ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1
+ ),
+ expectedIsInPreview = true,
+ page = 0,
+ rank = i
+ )
+ }
+ for (i in 4..40) {
+ assertItemsInPreview(
+ TestCase(
+ maxCountX = 5,
+ maxCountY = 8,
+ totalItems = ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1
+ ),
+ expectedIsInPreview = false,
+ page = 0,
+ rank = i
+ )
+ }
+ // Full of items
+ assertItemsInPreview(
+ TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+ expectedIsInPreview = false,
+ page = 0,
+ rank = 2
+ )
+ assertItemsInPreview(
+ TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+ expectedIsInPreview = false,
+ page = 0,
+ rank = 2
+ )
+ assertItemsInPreview(
+ TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+ expectedIsInPreview = true,
+ page = 0,
+ rank = 5
+ )
+ assertItemsInPreview(
+ TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+ expectedIsInPreview = true,
+ page = 0,
+ rank = 6
+ )
+ }
+
+ private fun assertItemsInPreview(
+ testCase: TestCase,
+ expectedIsInPreview: Boolean,
+ page: Int,
+ rank: Int
+ ) {
+ val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+ val isInPreview = folderGridOrganizer.isItemInPreview(page, rank)
+ assert(isInPreview == expectedIsInPreview) {
+ "Item preview state should be $expectedIsInPreview but got $isInPreview, for page $page and rank $rank, for test case $testCase"
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
new file mode 100644
index 0000000..d9af07a
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.AppFilter
+import com.android.launcher3.Flags.FLAG_ENABLE_PRIVATE_SPACE
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.PackageUpdatedTask.OP_ADD
+import com.android.launcher3.model.PackageUpdatedTask.OP_REMOVE
+import com.android.launcher3.model.PackageUpdatedTask.OP_SUSPEND
+import com.android.launcher3.model.PackageUpdatedTask.OP_UNAVAILABLE
+import com.android.launcher3.model.PackageUpdatedTask.OP_UNSUSPEND
+import com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE
+import com.android.launcher3.model.PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class PackageUpdatedTaskTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val mUser = UserHandle(0)
+ private val mDataModel: BgDataModel = BgDataModel()
+ private val mLauncherModelHelper = LauncherModelHelper()
+ private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
+ private val mAppState: LauncherAppState = spy(LauncherAppState.getInstance(mContext))
+
+ private val expectedPackage = "Test.Package"
+ private val expectedComponent = ComponentName(expectedPackage, "TestClass")
+ private val expectedActivityInfo: LauncherActivityInfo = mock<LauncherActivityInfo>()
+ private val expectedWorkspaceItem = spy(WorkspaceItemInfo())
+
+ private val mockIconCache: IconCache = mock()
+ private val mockTaskController: ModelTaskController = mock<ModelTaskController>()
+ private val mockAppFilter: AppFilter = mock<AppFilter>()
+ private val mockApplicationInfo: ApplicationInfo = mock<ApplicationInfo>()
+ private val mockActivityInfo: ActivityInfo = mock<ActivityInfo>()
+
+ private lateinit var mAllAppsList: AllAppsList
+
+ @Before
+ fun setup() {
+ mAllAppsList = spy(AllAppsList(mockIconCache, mockAppFilter))
+ mLauncherModelHelper.sandboxContext.spyService(LauncherApps::class.java).apply {
+ whenever(getActivityList(expectedPackage, mUser))
+ .thenReturn(listOf(expectedActivityInfo))
+ }
+ whenever(mAppState.iconCache).thenReturn(mockIconCache)
+ whenever(mockTaskController.app).thenReturn(mAppState)
+ whenever(mockAppFilter.shouldShowApp(expectedComponent)).thenReturn(true)
+ mockApplicationInfo.apply {
+ uid = 1
+ isArchived = false
+ }
+ mockActivityInfo.isArchived = false
+ expectedActivityInfo.apply {
+ whenever(applicationInfo).thenReturn(mockApplicationInfo)
+ whenever(activityInfo).thenReturn(mockActivityInfo)
+ whenever(componentName).thenReturn(expectedComponent)
+ }
+ expectedWorkspaceItem.apply {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ user = mUser
+ whenever(targetPackage).thenReturn(expectedPackage)
+ whenever(targetComponent).thenReturn(expectedComponent)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ mLauncherModelHelper.destroy()
+ }
+
+ @Test
+ fun `OP_ADD triggers model callbacks and adds new items to AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_ADD, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser)
+ verify(mAllAppsList).addPackage(mContext, expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ verify(mockTaskController).bindUpdatedWidgets(mDataModel)
+ assertThat(mAllAppsList.data.firstOrNull()?.componentName)
+ .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName)
+ }
+
+ @Test
+ fun `OP_UPDATE triggers model callbacks and updates items in AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_UPDATE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser)
+ verify(mAllAppsList).updatePackage(mContext, expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.data.firstOrNull()?.componentName)
+ .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName)
+ }
+
+ @Test
+ fun `OP_REMOVE triggers model callbacks and removes packages and icons`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_REMOVE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mockIconCache).removeIconsForPkg(expectedPackage, mUser)
+ verify(mAllAppsList).removePackage(expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.data).isEmpty()
+ }
+
+ @Test
+ fun `OP_UNAVAILABLE triggers model callbacks and removes package from AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_UNAVAILABLE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).removePackage(expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.data).isEmpty()
+ }
+
+ @Test
+ fun `OP_SUSPEND triggers model callbacks and updates flags in AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_SUSPEND, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ mAllAppsList.add(AppInfo(mContext, expectedActivityInfo, mUser), expectedActivityInfo)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).updateDisabledFlags(any(), any())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.getAndResetChangeFlag()).isTrue()
+ }
+
+ @Test
+ fun `OP_UNSUSPEND triggers no callbacks when app not suspended`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_UNSUSPEND, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).updateDisabledFlags(any(), any())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList())
+ assertThat(mAllAppsList.getAndResetChangeFlag()).isFalse()
+ }
+
+ @EnableFlags(FLAG_ENABLE_PRIVATE_SPACE)
+ @Test
+ fun `OP_USER_AVAILABILITY_CHANGE triggers no callbacks if current user not work or private`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_USER_AVAILABILITY_CHANGE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).updateDisabledFlags(any(), any())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList())
+ assertThat(mAllAppsList.data).isEmpty()
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 3d253b4..c926ba9 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -245,7 +245,7 @@
Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
mDevice.executeShellCommand("pm clear " + pkg);
- assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
+ assertTrue(pkg + " didn't restart", count.await(20, TimeUnit.SECONDS));
mTargetContext.unregisterReceiver(broadcastReceiver);
}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index eb05000..20c5a25 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -20,8 +20,6 @@
import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.MESSAGES_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -40,7 +38,6 @@
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.rule.ScreenRecordRule;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.After;
import org.junit.Before;
@@ -288,6 +285,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/330232490
public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() {
Workspace workspace = mLauncher.getWorkspace();
diff --git a/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt b/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
new file mode 100644
index 0000000..011d138
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Launcher
+import com.android.launcher3.Workspace
+import com.android.launcher3.pageindicators.PageIndicator
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verifyZeroInteractions
+
+@RunWith(AndroidJUnit4::class)
+class ViewOnDrawExecutorTest<T> where T : View, T : PageIndicator {
+
+ @Mock private lateinit var runnable: Runnable
+ @Mock private lateinit var consumer: Consumer<ViewOnDrawExecutor>
+ @Mock private lateinit var launcher: Launcher
+ @Mock private lateinit var workspace: Workspace<T>
+ @Mock private lateinit var rootView: View
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+ private lateinit var underTest: ViewOnDrawExecutor
+ private lateinit var runnableList: RunnableList
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ runnableList = RunnableList()
+ runnableList.add(runnable)
+ underTest = ViewOnDrawExecutor(runnableList, consumer)
+
+ `when`(launcher.workspace).thenReturn(workspace)
+ `when`(workspace.rootView).thenReturn(rootView)
+ `when`(workspace.viewTreeObserver).thenReturn(viewTreeObserver)
+ }
+
+ @Test
+ fun attachToLauncher_alreadyAttachedToWindow() {
+ `when`(workspace.isAttachedToWindow).thenReturn(true)
+
+ underTest.attachTo(launcher)
+
+ verify(workspace).addOnAttachStateChangeListener(same(underTest))
+ verify(viewTreeObserver).addOnDrawListener(same(underTest))
+ verify(rootView).invalidate()
+ }
+
+ @Test
+ fun attachToLauncher_notAttachedToWindow() {
+ `when`(workspace.isAttachedToWindow).thenReturn(false)
+
+ underTest.attachTo(launcher)
+
+ verify(workspace).addOnAttachStateChangeListener(same(underTest))
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(rootView)
+ }
+
+ @Test
+ fun onViewAttachedToWindow_registerObserver() {
+ `when`(workspace.isAttachedToWindow).thenReturn(false)
+ underTest.attachTo(launcher)
+
+ underTest.onViewAttachedToWindow(rootView)
+
+ verify(viewTreeObserver).addOnDrawListener(same(underTest))
+ verify(rootView).invalidate()
+ }
+
+ @Test
+ fun complete_then_onViewAttachedToWindow_registerObserver() {
+ underTest.markCompleted()
+ reset(viewTreeObserver)
+ reset(rootView)
+
+ underTest.onViewAttachedToWindow(rootView)
+
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(rootView)
+ }
+
+ @Test
+ fun onDraw_postRunnable() {
+ underTest.attachTo(launcher)
+
+ underTest.onDraw()
+
+ verify(workspace).post(same(underTest))
+ }
+
+ @Test
+ fun run_before_onDraw_noOp() {
+ underTest.run()
+
+ verifyZeroInteractions(runnable)
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(workspace)
+ verifyZeroInteractions(consumer)
+ }
+
+ @Test
+ fun first_run_executeRunnable() {
+ underTest.attachTo(launcher)
+ underTest.onDraw()
+
+ underTest.run()
+
+ verify(runnable).run()
+ verify(viewTreeObserver).removeOnDrawListener(same(underTest))
+ verify(workspace).removeOnAttachStateChangeListener(same(underTest))
+ verify(consumer).accept(same(underTest))
+ }
+
+ @Test
+ fun second_run_noOp() {
+ underTest.attachTo(launcher)
+ underTest.onDraw()
+ underTest.run()
+ reset(runnable)
+ reset(viewTreeObserver)
+ reset(workspace)
+ reset(consumer)
+
+ underTest.run()
+
+ verifyZeroInteractions(runnable)
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(workspace)
+ verifyZeroInteractions(consumer)
+ }
+
+ @Test
+ fun markCompleted_viewNotAttached() {
+ underTest.markCompleted()
+
+ verify(runnable).run()
+ verify(consumer).accept(underTest)
+ verifyZeroInteractions(workspace)
+ }
+
+ @Test
+ fun markCompleted_viewAttached() {
+ underTest.attachTo(launcher)
+
+ underTest.markCompleted()
+
+ verify(runnable).run()
+ verify(consumer).accept(underTest)
+ verify(workspace).removeOnAttachStateChangeListener(same(underTest))
+ verify(viewTreeObserver).removeOnDrawListener(same(underTest))
+ }
+
+ @Test
+ fun cancel_notRun() {
+ underTest.cancel()
+
+ verifyZeroInteractions(runnable)
+ verify(consumer).accept(underTest)
+ verifyZeroInteractions(workspace)
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f02a0c2..ad37f7b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -98,6 +98,7 @@
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -455,10 +456,6 @@
getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on));
}
- public void setEnableSuggestion(boolean enableSuggestion) {
- getTestInfo(TestProtocol.REQUEST_ENABLE_SUGGESTION, Boolean.toString(enableSuggestion));
- }
-
public boolean hadNontestEvents() {
return getTestInfo(TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS)
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -2402,6 +2399,16 @@
disableSensorRotation();
final Integer initialPid = getPid();
final LogEventChecker eventChecker = new LogEventChecker(this);
+ eventChecker.setLogExclusionRule(event -> {
+ Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event);
+ if (matcher.find()) {
+ int keyEventFlags = Integer.parseInt(matcher.group(1), 16);
+ // ignore KeyEvents with FLAG_CANCELED
+ return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0;
+ }
+ return false;
+ });
+
if (eventChecker.start()) mEventChecker = eventChecker;
return () -> {
@@ -2459,7 +2466,8 @@
}
float getWindowCornerRadius() {
- // TODO(b/197326121): Check if the touch is overlapping with the corners by offsetting
+ // Return a larger corner radius to ensure gesture calculated from the radius are offset to
+ // prevent overlapping
final float tmpBuffer = 100f;
final Resources resources = getResources();
if (!supportsRoundedCornersOnWindows(resources)) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 055a357..3a49160 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -35,6 +35,8 @@
// Map from an event sequence name to an ordered list of expected events in that sequence.
private final ListMap<Pattern> mExpectedEvents = new ListMap<>();
+ private LogExclusionRule mLogExclusionRule = null;
+
LogEventChecker(LauncherInstrumentation launcher) {
mLauncher = launcher;
}
@@ -48,6 +50,10 @@
mExpectedEvents.add(sequence, pattern);
}
+ void setLogExclusionRule(LogExclusionRule logExclusionRule) {
+ mLogExclusionRule = logExclusionRule;
+ }
+
// Waits for the expected number of events and returns them.
private ListMap<String> finishSync(long waitForExpectedCountMs) {
final long startTime = SystemClock.uptimeMillis();
@@ -74,7 +80,9 @@
final ListMap<String> eventSequences = new ListMap<>();
for (String rawEvent : rawEvents) {
final String[] split = rawEvent.split("/");
- eventSequences.add(split[0], split[1]);
+ if (mLogExclusionRule == null || !mLogExclusionRule.shouldExclude(split[1])) {
+ eventSequences.add(split[0], split[1]);
+ }
}
return eventSequences;
}
@@ -175,4 +183,8 @@
return list;
}
}
+
+ interface LogExclusionRule {
+ boolean shouldExclude(String event);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 9ac6768..748d576 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -345,17 +345,34 @@
* @return map of text -> center of the view. In case of icons with the same name, the one with
* lower x coordinate is selected.
*/
- public Map<String, Point> getWorkspaceIconsPositions() {
+ public Map<String, Point> getAllWorkspaceIconsPositions() {
final UiObject2 workspace = verifyActiveContainer();
- mLauncher.waitForLauncherInitialized(); // b/319501259
List<UiObject2> workspaceIcons =
mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
- return workspaceIcons.stream()
+ return getIconPositionMap(workspaceIcons);
+ }
+
+ /**
+ * @return point where icon is found for given the app name,
+ * point is visible center of the icon.
+ */
+ @NonNull
+ public Point getWorkspaceIconPosition(String appName) {
+ final UiObject2 workspace = verifyActiveContainer();
+
+ UiObject2 workspaceIcon =
+ mLauncher.waitForObjectInContainer(workspace,
+ AppIcon.getAppIconSelector(appName, mLauncher));
+ return workspaceIcon.getVisibleCenter();
+ }
+
+ private Map<String, Point> getIconPositionMap(List<UiObject2> icons) {
+ return icons.stream()
.collect(
Collectors.toMap(
/* keyMapper= */ uiObject21 -> {
- Log.d(UIOBJECT_STALE_ELEMENT, "keyText: " +
- uiObject21.getText());
+ Log.d(UIOBJECT_STALE_ELEMENT, "keyText: "
+ + uiObject21.getText());
return uiObject21.getText();
},
/* valueMapper= */ uiObject2 -> {