Merge "Disable floating search bar flag by default."
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 12daa59..d7de8a7 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -40,6 +40,7 @@
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.VIBRATE"/>
<!-- for rotating surface by arbitrary degree -->
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
diff --git a/build.gradle b/build.gradle
index 68ed73d..090bafe 100644
--- a/build.gradle
+++ b/build.gradle
@@ -147,6 +147,7 @@
implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
implementation project(':IconLoader')
+ implementation project(':UiTestsLibLauncher')
withQuickstepImplementation project(':SharedLibWrapper')
// Recents lib dependency
diff --git a/quickstep/res/drawable/hotseat_icon.xml b/quickstep/res/drawable/hotseat_icon.xml
new file mode 100644
index 0000000..b849fe9
--- /dev/null
+++ b/quickstep/res/drawable/hotseat_icon.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/mock_app_icon" />
+ <corners android:radius="@dimen/gesture_tutorial_hotseat_icon_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/hotseat_icon_home.xml b/quickstep/res/drawable/hotseat_icon_home.xml
new file mode 100644
index 0000000..d59dd4a
--- /dev/null
+++ b/quickstep/res/drawable/hotseat_icon_home.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/gesture_home_tutorial_background" />
+ <corners android:radius="@dimen/gesture_tutorial_hotseat_icon_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/hotseat_search_bar.xml b/quickstep/res/drawable/hotseat_search_bar.xml
new file mode 100644
index 0000000..ea332e9
--- /dev/null
+++ b/quickstep/res/drawable/hotseat_search_bar.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/mock_search_bar" />
+ <corners android:radius="@dimen/gesture_tutorial_hotseat_search_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/top_task_view.xml b/quickstep/res/drawable/top_task_view.xml
new file mode 100644
index 0000000..d2176c3
--- /dev/null
+++ b/quickstep/res/drawable/top_task_view.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/gesture_tutorial_fake_previous_task_view_color" />
+ <corners android:radius="@dimen/gesture_tutorial_small_task_view_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/layout-land/gesture_tutorial_mock_hotseat.xml b/quickstep/res/layout-land/gesture_tutorial_mock_hotseat.xml
index 1e2e014..c7e176a 100644
--- a/quickstep/res/layout-land/gesture_tutorial_mock_hotseat.xml
+++ b/quickstep/res/layout-land/gesture_tutorial_mock_hotseat.xml
@@ -24,54 +24,50 @@
android:paddingStart="56dp"
android:paddingEnd="56dp">
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintVertical_chainStyle="spread_inside"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_2"
app:layout_constraintStart_toStartOf="parent"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1"
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_3"
app:layout_constraintStart_toStartOf="parent"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_2"
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_4"
app:layout_constraintStart_toStartOf="parent"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_3"
app:layout_constraintBottom_toBottomOf="parent"
diff --git a/quickstep/res/layout-land/gesture_tutorial_tablet_mock_hotseat.xml b/quickstep/res/layout-land/gesture_tutorial_tablet_mock_hotseat.xml
index f04fbb6..28d32a4 100644
--- a/quickstep/res/layout-land/gesture_tutorial_tablet_mock_hotseat.xml
+++ b/quickstep/res/layout-land/gesture_tutorial_tablet_mock_hotseat.xml
@@ -22,84 +22,77 @@
android:paddingStart="@dimen/gesture_tutorial_hotseat_padding_start_end"
android:paddingEnd="@dimen/gesture_tutorial_hotseat_padding_start_end">
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_search_bar"
android:layout_width="200dp"
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
+ android:background="@drawable/hotseat_search_bar"
+ android:clipToOutline="true"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
- app:cardBackgroundColor="@color/mock_search_bar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_1"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_search_bar"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_5"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_5"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 8eeef67..3bd0df0 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -57,33 +57,34 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
- <androidx.cardview.widget.CardView
+
+ <View
android:id="@+id/top_task_view"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:visibility="invisible"
android:layout_marginBottom="@dimen/gesture_tutorial_multi_row_task_view_spacing"
+ android:background="@drawable/top_task_view"
+ android:clipToOutline="true"
+ android:visibility="invisible"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
app:layout_constraintVertical_chainStyle="spread"
- app:layout_constraintTop_toTopOf="@id/full_task_view"
app:layout_constraintBottom_toTopOf="@id/bottom_task_view"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintTop_toTopOf="@id/full_task_view" />
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/bottom_task_view"
android:layout_width="match_parent"
android:layout_height="0dp"
+ android:background="@drawable/top_task_view"
+ android:clipToOutline="true"
android:visibility="invisible"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
- app:layout_constraintTop_toBottomOf="@id/top_task_view"
app:layout_constraintBottom_toBottomOf="@id/full_task_view"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintTop_toBottomOf="@id/top_task_view" />
</com.android.quickstep.interaction.AnimatedTaskView>
diff --git a/quickstep/res/layout/gesture_tutorial_mock_hotseat.xml b/quickstep/res/layout/gesture_tutorial_mock_hotseat.xml
index 8513dcf..8ee0339 100644
--- a/quickstep/res/layout/gesture_tutorial_mock_hotseat.xml
+++ b/quickstep/res/layout/gesture_tutorial_mock_hotseat.xml
@@ -8,67 +8,62 @@
android:paddingStart="26dp"
android:paddingEnd="26dp">
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
app:layout_constraintEnd_toEndOf="parent"/>
- <androidx.cardview.widget.CardView
+ <View
android:layout_width="0dp"
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
+ android:background="@drawable/hotseat_search_bar"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
- app:cardBackgroundColor="@color/mock_search_bar"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
diff --git a/quickstep/res/layout/gesture_tutorial_tablet_mock_hotseat.xml b/quickstep/res/layout/gesture_tutorial_tablet_mock_hotseat.xml
index 363f14e..63c51e8 100644
--- a/quickstep/res/layout/gesture_tutorial_tablet_mock_hotseat.xml
+++ b/quickstep/res/layout/gesture_tutorial_tablet_mock_hotseat.xml
@@ -22,84 +22,78 @@
android:paddingStart="@dimen/gesture_tutorial_hotseat_padding_start_end"
android:paddingEnd="@dimen/gesture_tutorial_hotseat_padding_start_end">
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_search_bar"
android:layout_width="0dp"
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
+ android:background="@drawable/hotseat_search_bar"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
- app:cardBackgroundColor="@color/mock_search_bar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_5"/>
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_5"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
+ android:background="@drawable/hotseat_icon"
+ android:clipToOutline="true"
- app:cardElevation="0dp"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardBackgroundColor="@color/mock_app_icon"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_4"
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index 13b7238..13482ac 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -57,29 +57,29 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
- <androidx.cardview.widget.CardView
+
+ <View
android:id="@+id/top_task_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="@dimen/gesture_tutorial_multi_row_task_view_spacing"
+ android:background="@drawable/top_task_view"
+ android:clipToOutline="true"
android:visibility="invisible"
- app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
- app:cardElevation="0dp"
app:layout_constraintBottom_toTopOf="@id/bottom_task_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@id/full_task_view"
- app:layout_constraintVertical_chainStyle="spread" />
+ app:layout_constraintTop_toTopOf="@id/full_task_view" />
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/bottom_task_view"
android:layout_width="match_parent"
android:layout_height="0dp"
+ android:background="@drawable/top_task_view"
+ android:clipToOutline="true"
android:visibility="invisible"
- app:cardCornerRadius="@dimen/gesture_tutorial_small_task_view_corner_radius"
- app:cardElevation="0dp"
app:layout_constraintBottom_toBottomOf="@id/full_task_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -99,13 +99,13 @@
android:background="@drawable/gesture_tutorial_ripple" />
<include
- layout="@layout/gesture_tutorial_tablet_mock_taskbar"
android:id="@+id/gesture_tutorial_fake_taskbar_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/gesture_tutorial_mock_taskbar_height"
+ layout="@layout/gesture_tutorial_tablet_mock_taskbar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
- android:layout_alignParentStart="true"
- android:layout_alignParentEnd="true" />
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="@dimen/gesture_tutorial_taskbar_margin_bottom" />
<ImageView
android:id="@+id/gesture_tutorial_edge_gesture_video"
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml b/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml
index 6d9ffae..b1c8b31 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml
@@ -21,70 +21,64 @@
android:layout_height="wrap_content"
android:paddingBottom="70dp"
android:paddingStart="26dp"
- android:paddingEnd="26dp"
- android:background="@color/gesture_home_tutorial_swipe_up_rect">
+ android:paddingEnd="26dp">
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon_home"
+ android:clipToOutline="true"
- app:cardBackgroundColor="@color/gesture_home_tutorial_background"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardElevation="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon_home"
+ android:clipToOutline="true"
- app:cardBackgroundColor="@color/gesture_home_tutorial_background"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardElevation="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
app:layout_constraintTop_toTopOf="parent" />
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon_home"
+ android:clipToOutline="true"
- app:cardBackgroundColor="@color/gesture_home_tutorial_background"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardElevation="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
app:layout_constraintTop_toTopOf="parent" />
- <androidx.cardview.widget.CardView
+ <View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
+ android:background="@drawable/hotseat_icon_home"
+ android:clipToOutline="true"
- app:cardBackgroundColor="@color/gesture_home_tutorial_background"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_icon_corner_radius"
- app:cardElevation="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
app:layout_constraintTop_toTopOf="parent" />
- <androidx.cardview.widget.CardView
+ <View
android:layout_width="0dp"
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
+ android:background="@drawable/hotseat_icon_home"
+ android:clipToOutline="true"
- app:cardBackgroundColor="@color/gesture_home_tutorial_background"
- app:cardCornerRadius="@dimen/gesture_tutorial_hotseat_search_corner_radius"
- app:cardElevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1" />
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 0d8391b..3846a9c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -297,15 +297,11 @@
<!-- An additional touch slop to prevent x-axis movement during the swipe up to show taskbar -->
<dimen name="transient_taskbar_clamped_offset_bound">16dp</dimen>
<!-- Taskbar swipe up thresholds -->
- <dimen name="taskbar_nav_threshold">40dp</dimen>
- <dimen name="taskbar_app_window_threshold">150dp</dimen>
- <dimen name="taskbar_home_overview_threshold">225dp</dimen>
+ <dimen name="taskbar_nav_threshold">30dp</dimen>
+ <dimen name="taskbar_app_window_threshold">100dp</dimen>
+ <dimen name="taskbar_home_overview_threshold">180dp</dimen>
<dimen name="taskbar_catch_up_threshold">300dp</dimen>
- <dimen name="taskbar_nav_threshold_v2">30dp</dimen>
- <dimen name="taskbar_app_window_threshold_v2">100dp</dimen>
- <dimen name="taskbar_home_overview_threshold_v2">180dp</dimen>
-
<!-- Taskbar 3 button spacing -->
<dimen name="taskbar_button_space_inbetween">24dp</dimen>
<dimen name="taskbar_button_space_inbetween_phone">40dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index a82fee8..d2f5802 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -197,7 +197,7 @@
<!-- Title of "All Set" page [CHAR LIMIT=NONE] -->
<string name="allset_title">All set!</string>
<!-- Hint string at the bottom of "All Set" page [CHAR LIMIT=NONE] -->
- <string name="allset_hint">Swipe up to go Home</string>
+ <string name="allset_hint">Swipe up to go home</string>
<!-- Hint string at the bottom of "All Set" page for button navigation [CHAR LIMIT=NONE] -->
<string name="allset_button_hint">Tap the home button to go to your home screen</string>
<!-- Description of "All Set" page on the user's device [CHAR LIMIT=NONE] -->
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index ae121e2..d087d39 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.statehandlers;
import android.os.SystemProperties;
+import android.util.Log;
import android.view.View;
import com.android.launcher3.Launcher;
@@ -29,6 +30,9 @@
*/
public class DesktopVisibilityController {
+ private static final String TAG = "DesktopVisController";
+ private static final boolean DEBUG = false;
+
private final Launcher mLauncher;
private boolean mFreeformTasksVisible;
@@ -58,6 +62,9 @@
* Sets whether freeform windows are visible and updates launcher visibility based on that.
*/
public void setFreeformTasksVisible(boolean freeformTasksVisible) {
+ if (DEBUG) {
+ Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible);
+ }
if (!isDesktopModeSupported()) {
return;
}
@@ -83,6 +90,9 @@
* Sets whether the overview is visible and updates launcher visibility based on that.
*/
public void setOverviewStateEnabled(boolean overviewStateEnabled) {
+ if (DEBUG) {
+ Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled);
+ }
if (!isDesktopModeSupported()) {
return;
}
@@ -109,6 +119,9 @@
* Sets whether recents gesture is in progress.
*/
public void setGestureInProgress(boolean gestureInProgress) {
+ if (DEBUG) {
+ Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
+ }
if (!isDesktopModeSupported()) {
return;
}
@@ -118,6 +131,9 @@
}
private void setLauncherViewsVisibility(int visibility) {
+ if (DEBUG) {
+ Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
+ }
View workspaceView = mLauncher.getWorkspace();
if (workspaceView != null) {
workspaceView.setVisibility(visibility);
@@ -129,6 +145,9 @@
}
private void markLauncherPaused() {
+ if (DEBUG) {
+ Log.d(TAG, "markLauncherPaused");
+ }
StatefulActivity<LauncherState> activity =
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
if (activity != null) {
@@ -137,6 +156,9 @@
}
private void markLauncherResumed() {
+ if (DEBUG) {
+ Log.d(TAG, "markLauncherResumed");
+ }
StatefulActivity<LauncherState> activity =
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
// Check activity state before calling setResumed(). Launcher may have been actually
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 5537878..124f15e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -800,7 +800,7 @@
@Override
public void setResumed() {
- if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
DesktopVisibilityController controller = mDesktopVisibilityController;
if (controller != null && controller.areFreeformTasksVisible()
&& !controller.isGestureInProgress()) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index b5afda3..df95dc1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -39,6 +39,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -62,6 +63,7 @@
private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
+ private final VibratorWrapper mVibratorWrapper;
private final RecentsView mRecentsView;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
@@ -82,6 +84,7 @@
mRecentsView = l.getOverviewPanel();
mMotionPauseDetector = new MotionPauseDetector(l);
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
+ mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
}
@Override
@@ -188,6 +191,11 @@
// need to manually set the duration to a reasonable value.
animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
}
+ if (FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() &&
+ ((mFromState == NORMAL && mToState == ALL_APPS)
+ || (mFromState == ALL_APPS && mToState == NORMAL)) && isFling) {
+ mVibratorWrapper.vibrateForDragBump();
+ }
}
private void onMotionPauseDetected() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 7384857..384430e 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -27,7 +27,6 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_REVISED_THRESHOLDS;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -366,17 +365,12 @@
TaskbarUIController controller = mActivityInterface.getTaskbarController();
mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
- mTaskbarAppWindowThreshold = res
- .getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
- ? R.dimen.taskbar_app_window_threshold_v2
- : R.dimen.taskbar_app_window_threshold);
+ mTaskbarAppWindowThreshold =
+ res.getDimensionPixelSize(R.dimen.taskbar_app_window_threshold);
boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen;
mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
? 0
- : res.getDimensionPixelSize(
- ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
- ? R.dimen.taskbar_home_overview_threshold_v2
- : R.dimen.taskbar_home_overview_threshold);
+ : res.getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold);
mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold);
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 274b686..f9ad749 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
@@ -62,6 +63,7 @@
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -107,6 +109,20 @@
if (endTarget != null) {
// We were on our way to this state when we got canceled, end there instead.
startState = stateFromGestureEndTarget(endTarget);
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ DesktopVisibilityController controller = getDesktopVisibilityController();
+ if (controller != null && controller.areFreeformTasksVisible()
+ && endTarget == LAST_TASK) {
+ // When we are cancelling the transition and going back to last task, move to
+ // rest state instead when desktop tasks are visible.
+ // If a fullscreen task is visible, launcher goes to normal state when the
+ // activity is stopped. This does not happen when freeform tasks are visible
+ // on top of launcher. Force the launcher state to rest state here.
+ startState = activity.getStateManager().getRestState();
+ // Do not animate the transition
+ activityVisible = false;
+ }
+ }
}
activity.getStateManager().goToState(startState, activityVisible);
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index cf5082f..d3b4e49 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -632,14 +632,20 @@
}
}
- public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Bundle options1,
- PendingIntent pendingIntent2, Bundle options2,
- @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ /**
+ * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a
+ * non-null shortcut info means to start the app as a shortcut.
+ */
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitConfigurationOptions.StagePosition int sidePosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
- mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, options1,
- pendingIntent2, options2, sidePosition, splitRatio, adapter, instanceId);
+ mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
+ options1, pendingIntent2, shortcutInfo2, options2, sidePosition, splitRatio,
+ adapter, instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntentsWithLegacyTransition");
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 0e5aa81..9872627 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -40,6 +40,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -238,6 +239,12 @@
// to let the transition controller collect Home activity.
CachedTaskInfo cti = gestureState.getRunningTask();
boolean homeIsOnTop = cti != null && cti.isHomeTask();
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ if (cti != null && cti.isFreeformTask()) {
+ // No transient launch when desktop task is on top
+ homeIsOnTop = true;
+ }
+ }
if (!homeIsOnTop) {
options.setTransientLaunch();
}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index d4bf5c7..6d8ee10 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -18,6 +18,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.Intent.ACTION_CHOOSER;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -255,6 +256,15 @@
}
/**
+ * Returns {@code true} if this task windowing mode is set to {@link
+ * android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
+ */
+ public boolean isFreeformTask() {
+ return mTopTask != null && mTopTask.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+
+ /**
* Returns {@link Task} array which can be used as a placeholder until the true object
* is loaded by the model
*/
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
index 385debb..992618a 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.Utilities.isTrackpadMotionEvent;
import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_REVISED_THRESHOLDS;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING;
import android.content.Context;
@@ -75,9 +74,7 @@
Resources res = context.getResources();
mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
- int taskbarNavThreshold = res.getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
- ? R.dimen.taskbar_nav_threshold_v2
- : R.dimen.taskbar_nav_threshold);
+ int taskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
int screenHeight = taskbarActivityContext.getDeviceProfile().heightPx;
mTaskbarNavThresholdY = screenHeight - taskbarNavThreshold;
mIsTaskbarAllAppsOpen =
diff --git a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java
index 53ad138..3ccd683 100644
--- a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java
+++ b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java
@@ -30,7 +30,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
@@ -46,8 +45,8 @@
public class AnimatedTaskView extends ConstraintLayout {
private View mFullTaskView;
- private CardView mTopTaskView;
- private CardView mBottomTaskView;
+ private View mTopTaskView;
+ private View mBottomTaskView;
private ViewOutlineProvider mTaskViewOutlineProvider = null;
private final Rect mTaskViewAnimatedRect = new Rect();
@@ -185,8 +184,6 @@
void setFakeTaskViewFillColor(@ColorInt int colorResId) {
mFullTaskView.setBackgroundColor(colorResId);
- mTopTaskView.setCardBackgroundColor(colorResId);
- mBottomTaskView.setCardBackgroundColor(colorResId);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index c89d4b6..bce639b 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -61,10 +61,10 @@
@Override
protected int getMockAppTaskLayoutResId() {
- return mTutorialFragment.isLargeScreen()
- ? R.layout.gesture_tutorial_tablet_mock_webpage
- : ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
- ? R.layout.swipe_up_gesture_tutorial_shape
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.layout.swipe_up_gesture_tutorial_shape
+ : mTutorialFragment.isLargeScreen()
+ ? R.layout.gesture_tutorial_tablet_mock_webpage
: R.layout.gesture_tutorial_mock_webpage;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index ccdb266..6fcb840 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -154,7 +154,6 @@
mFeedbackTitleView.setText(getIntroductionTitle());
mFeedbackSubtitleView.setText(getIntroductionSubtitle());
- mSkipButton.setVisibility(GONE);
}
mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent(
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 4ca41b5..82da657 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -262,8 +262,9 @@
getOppositeStagePosition(stagePosition), splitRatio, adapter,
shellInstanceId);
} else {
- mSystemUiProxy.startIntentsWithLegacyTransition(getPendingIntent(intent1),
- options1.toBundle(), getPendingIntent(intent2), null /* options2 */,
+ mSystemUiProxy.startIntentsWithLegacyTransition(
+ getPendingIntent(intent1), getShortcutInfo(intent1), options1.toBundle(),
+ getPendingIntent(intent2), getShortcutInfo(intent2), null /* options2 */,
stagePosition, splitRatio, adapter, shellInstanceId);
}
}
@@ -272,15 +273,13 @@
private void launchIntentOrShortcut(Intent intent, ActivityOptions options1, int taskId,
@StagePosition int stagePosition, float splitRatio, RemoteTransition remoteTransition,
@Nullable InstanceId shellInstanceId) {
- PendingIntent pendingIntent = getPendingIntent(intent);
- final ShortcutInfo shortcutInfo = getShortcutInfo(intent,
- pendingIntent.getCreatorUserHandle());
+ final ShortcutInfo shortcutInfo = getShortcutInfo(intent);
if (shortcutInfo != null) {
mSystemUiProxy.startShortcutAndTask(shortcutInfo,
options1.toBundle(), taskId, null /* options2 */, stagePosition,
splitRatio, remoteTransition, shellInstanceId);
} else {
- mSystemUiProxy.startIntentAndTask(pendingIntent, options1.toBundle(), taskId,
+ mSystemUiProxy.startIntentAndTask(getPendingIntent(intent), options1.toBundle(), taskId,
null /* options2 */, stagePosition, splitRatio, remoteTransition,
shellInstanceId);
}
@@ -289,15 +288,13 @@
private void launchIntentOrShortcutLegacy(Intent intent, ActivityOptions options1, int taskId,
@StagePosition int stagePosition, float splitRatio, RemoteAnimationAdapter adapter,
@Nullable InstanceId shellInstanceId) {
- PendingIntent pendingIntent = getPendingIntent(intent);
- final ShortcutInfo shortcutInfo = getShortcutInfo(intent,
- pendingIntent.getCreatorUserHandle());
+ final ShortcutInfo shortcutInfo = getShortcutInfo(intent);
if (shortcutInfo != null) {
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
options1.toBundle(), taskId, null /* options2 */, stagePosition,
splitRatio, adapter, shellInstanceId);
} else {
- mSystemUiProxy.startIntentAndTaskWithLegacyTransition(pendingIntent,
+ mSystemUiProxy.startIntentAndTaskWithLegacyTransition(getPendingIntent(intent),
options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio,
adapter, shellInstanceId);
}
@@ -324,7 +321,7 @@
}
@Nullable
- private ShortcutInfo getShortcutInfo(Intent intent, UserHandle userHandle) {
+ private ShortcutInfo getShortcutInfo(Intent intent) {
if (intent == null || intent.getPackage() == null) {
return null;
}
@@ -336,7 +333,7 @@
try {
final Context context = mContext.createPackageContextAsUser(
- intent.getPackage(), 0 /* flags */, userHandle);
+ intent.getPackage(), 0 /* flags */, mUser);
return new ShortcutInfo.Builder(context, shortcutId).build();
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage());
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 3f7d677..5cf79ea 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -355,6 +355,12 @@
mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
}
+ @Override
+ protected void refreshTaskThumbnailSplash() {
+ super.refreshTaskThumbnailSplash();
+ mSnapshotView2.refreshSplashView();
+ }
+
/**
* Sets visibility for thumbnails and associated elements (DWB banners).
* IconView is unaffected.
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 886fc80..ff26129 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -41,8 +41,10 @@
import com.android.launcher3.util.PendingSplitSelectInfo;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.quickstep.GestureState;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.systemui.shared.recents.model.Task;
@@ -92,6 +94,7 @@
@Override
public void onTaskIconChanged(int taskId) {
+ super.onTaskIconChanged(taskId);
// If Launcher needs to return to split select state, do it now, after the icon has updated.
if (mActivity.hasPendingSplitSelectInfo()) {
PendingSplitSelectInfo recoveryData = mActivity.getPendingSplitSelectInfo();
@@ -222,11 +225,23 @@
@Override
public void onGestureAnimationEnd() {
+ DesktopVisibilityController desktopVisibilityController = null;
+ boolean showDesktopApps = false;
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ desktopVisibilityController = mActivity.getDesktopVisibilityController();
+ if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.LAST_TASK
+ && desktopVisibilityController.areFreeformTasksVisible()) {
+ // Recents gesture was cancelled and we are returning to the previous task.
+ // After super class has handled clean up, show desktop apps on top again
+ showDesktopApps = true;
+ }
+ }
super.onGestureAnimationEnd();
- DesktopVisibilityController desktopVisibilityController =
- mActivity.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
desktopVisibilityController.setGestureInProgress(false);
}
+ if (showDesktopApps) {
+ SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps();
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2505d91..5b40849 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -956,6 +956,14 @@
}
}
+ @Override
+ public void onTaskIconChanged(int taskId) {
+ TaskView taskView = getTaskViewByTaskId(taskId);
+ if (taskView != null) {
+ taskView.refreshTaskThumbnailSplash();
+ }
+ }
+
/**
* Update the thumbnail of the task.
* @param refreshNow Refresh immediately if it's true.
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index c71a74e..432eadc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -342,10 +342,11 @@
// Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
if (shouldShowSplashView()) {
+ // Always draw background for hiding inconsistencies, even if splash view is not yet
+ // loaded (which can happen as task icons are loaded asynchronously in the background)
+ canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
+ cornerRadius, mSplashBackgroundPaint);
if (mSplashView != null) {
- canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
- cornerRadius, mSplashBackgroundPaint);
-
mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
mSplashView.draw(canvas);
}
@@ -375,6 +376,13 @@
|| isThumbnailRotationDifferentFromTask();
}
+ protected void refreshSplashView() {
+ if (mTask != null) {
+ updateSplashView(mTask.icon);
+ invalidate();
+ }
+ }
+
private void updateSplashView(Drawable icon) {
if (icon == null || icon.getConstantState() == null) {
mSplashViewDrawable = null;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 19ffa27..829e72c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1215,6 +1215,10 @@
mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha);
}
+ protected void refreshTaskThumbnailSplash() {
+ mSnapshotView.refreshSplashView();
+ }
+
private void setSplitSelectTranslationX(float x) {
mSplitSelectTranslationX = x;
applyTranslationX();
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index 9afd893..bc1b87d 100644
--- a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -19,7 +19,7 @@
import android.graphics.RectF
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.launcher3.DeviceProfileBaseTest
+import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
import com.android.quickstep.views.TaskView.FullscreenDrawParams
@@ -36,7 +36,7 @@
/** Test for FullscreenDrawParams class. */
@SmallTest
@RunWith(AndroidJUnit4::class)
-class FullscreenDrawParamsTest : DeviceProfileBaseTest() {
+class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
private val TASK_SCALE = 0.7f
private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
index adbca32..a347156 100644
--- a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
@@ -18,7 +18,7 @@
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.launcher3.DeviceProfileBaseTest
+import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.WindowBounds
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -26,7 +26,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
+class HotseatWidthCalculationTest : FakeInvariantDeviceProfileTest() {
/**
* This is a case when after setting the hotseat, the space needs to be recalculated but it
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index f2ca844..1714610 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -193,7 +193,7 @@
mLauncher.goHome().switchToOverview().getCurrentTask()
.tapMenu()
.tapSplitMenuItem()
- .getTestActivityTask(2)
+ .getCurrentTask()
.open();
}
diff --git a/res/layout/workspace_screen_foldable.xml b/res/layout/workspace_screen_foldable.xml
new file mode 100644
index 0000000..1e01250
--- /dev/null
+++ b/res/layout/workspace_screen_foldable.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<com.android.launcher3.MultipageCellLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:hapticFeedbackEnabled="false"
+ launcher:containerType="workspace" />
\ No newline at end of file
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index 63970cd..92f6881 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -30,6 +30,4 @@
<dimen name="dynamic_grid_edge_margin">11.33dp</dimen>
<dimen name="cell_layout_padding">11.33dp</dimen>
-<!-- AllApps -->
- <dimen name="all_apps_bottom_sheet_horizontal_padding">52dp</dimen>
</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index d69e777..0af0603 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -26,7 +26,6 @@
<!-- AllApps -->
<dimen name="all_apps_search_bar_content_overlap">0dp</dimen>
- <dimen name="all_apps_bottom_sheet_horizontal_padding">48dp</dimen>
<!-- Fast scroll -->
<dimen name="fastscroll_popup_width">75dp</dimen>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
index 235631d..6362960 100644
--- a/res/values-sw720dp-land/dimens.xml
+++ b/res/values-sw720dp-land/dimens.xml
@@ -22,9 +22,6 @@
<dimen name="dynamic_grid_edge_margin">21.93dp</dimen>
<dimen name="cell_layout_padding">29.33dp</dimen>
-<!-- AllApps -->
- <dimen name="all_apps_bottom_sheet_horizontal_padding">32dp</dimen>
-
<!-- Dragging-->
<dimen name="drop_target_top_margin">0dp</dimen>
<dimen name="drop_target_bottom_margin">32dp</dimen>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 09b2d6f..d7a5881 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -18,9 +18,6 @@
<!-- PagedView -->
<dimen name="min_page_snap_velocity">3400dp</dimen>
-<!-- AllApps -->
- <dimen name="all_apps_bottom_sheet_horizontal_padding">28dp</dimen>
-
<!-- Dynamic grid -->
<dimen name="dynamic_grid_edge_margin">27.59dp</dimen>
<dimen name="cell_layout_padding">36dp</dimen>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e6f285c..c3bd90e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -151,6 +151,9 @@
<!-- Support attributes in FolderStyle -->
<attr name="folderStyle" format="reference" />
+ <!-- Support attributes in AllAppsStyle. Defaults to AllAppsStyleDefault -->
+ <attr name="allAppsStyle" format="reference" />
+
<!-- numAllAppsColumns defaults to numColumns, if not specified -->
<attr name="numAllAppsColumns" format="integer" />
<!-- Number of columns to use when extending the all-apps size,
@@ -221,6 +224,21 @@
<attr name="alignOnIcon" format="boolean" />
</declare-styleable>
+ <!-- Responsive grids attributes -->
+ <declare-styleable name="WorkspaceSpec">
+ <attr name="specType" format="integer">
+ <enum name="height" value="0" />
+ <enum name="width" value="1" />
+ </attr>
+ <attr name="maxAvailableSize" format="dimension" />
+ </declare-styleable>
+
+ <declare-styleable name="SpecSize">
+ <attr name="fixedSize" format="dimension" />
+ <attr name="ofAvailableSpace" format="float" />
+ <attr name="ofRemainderSpace" format="float" />
+ </declare-styleable>
+
<declare-styleable name="ProfileDisplayOption">
<attr name="name" />
<attr name="minWidthDps" format="float" />
@@ -418,6 +436,10 @@
<attr name="iconDrawablePadding" format="dimension" />
</declare-styleable>
+ <declare-styleable name="AllAppsStyle">
+ <attr name="horizontalPadding" format="dimension" />
+ </declare-styleable>
+
<declare-styleable name="ShadowDrawable">
<attr name="android:src" />
<attr name="android:shadowColor" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 321aef5..a9638f9 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -131,7 +131,6 @@
<dimen name="all_apps_content_fade_in_offset">150dp</dimen>
<dimen name="all_apps_tip_bottom_margin">8dp</dimen>
<dimen name="all_apps_height_extra">6dp</dimen>
- <dimen name="all_apps_bottom_sheet_horizontal_padding">0dp</dimen>
<dimen name="all_apps_paged_view_top_padding">40dp</dimen>
<dimen name="all_apps_icon_drawable_padding">8dp</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6b57532..a0b5570 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -304,4 +304,8 @@
<item name="iconDrawablePadding">7dp</item>
</style>
+ <style name="AllAppsStyleDefault">
+ <item name="horizontalPadding">16dp</item>
+ </style>
+
</resources>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index d388ebc..b96e4df 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -99,9 +99,9 @@
private Point mBorderSpace;
@ViewDebug.ExportedProperty(category = "launcher")
- private int mCountX;
+ protected int mCountX;
@ViewDebug.ExportedProperty(category = "launcher")
- private int mCountY;
+ protected int mCountY;
private boolean mDropPending = false;
@@ -111,8 +111,8 @@
@Thunk final int[] mTempLocation = new int[2];
final PointF mTmpPointF = new PointF();
- private GridOccupancy mOccupied;
- private GridOccupancy mTmpOccupied;
+ protected GridOccupancy mOccupied;
+ protected GridOccupancy mTmpOccupied;
private OnTouchListener mInterceptTouchListener;
@@ -121,7 +121,7 @@
private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
private static final int[] BACKGROUND_STATE_DEFAULT = EMPTY_STATE_SET;
- private final Drawable mBackground;
+ protected final Drawable mBackground;
// These values allow a fixed measurement to be set on the CellLayout.
private int mFixedWidth = -1;
@@ -154,7 +154,7 @@
private int mGridVisualizationRoundingRadius;
private float mGridAlpha = 0f;
private int mGridColor = 0;
- private float mSpringLoadedProgress = 0f;
+ protected float mSpringLoadedProgress = 0f;
private float mScrollProgress = 0f;
// When a drag operation is in progress, holds the nearest cell to the touch point
@@ -164,7 +164,7 @@
private boolean mDragging = false;
private final TimeInterpolator mEaseOutInterpolator;
- private final ShortcutAndWidgetContainer mShortcutsAndWidgets;
+ protected final ShortcutAndWidgetContainer mShortcutsAndWidgets;
@Retention(RetentionPolicy.SOURCE)
@IntDef({WORKSPACE, HOTSEAT, FOLDER})
@@ -565,7 +565,7 @@
return mSpringLoadedProgress;
}
- private void updateBgAlpha() {
+ protected void updateBgAlpha() {
mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
}
@@ -1662,7 +1662,7 @@
* @param spanY vertical cell span
* @return the configuration that represents the found reorder
*/
- private ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
+ ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
int minSpanY, int spanX, int spanY) {
ItemConfiguration solution = new ItemConfiguration();
int[] result = new int[2];
@@ -2402,8 +2402,15 @@
return true;
}
- private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
- int spanX, int spanY, int[] direction, View dragView, boolean decX,
+ protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+ int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+ ItemConfiguration solution) {
+ return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
+ direction, dragView, decX, solution);
+ }
+
+ protected ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX,
+ int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
ItemConfiguration solution) {
// Copy the current state into the solution. This solution will be manipulated as necessary.
copyCurrentStateToSolution(solution, false);
@@ -2426,11 +2433,11 @@
// We try shrinking the widget down to size in an alternating pattern, shrink 1 in
// x, then 1 in y etc.
if (spanX > minSpanX && (minSpanY == spanY || decX)) {
- return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
- direction, dragView, false, solution);
+ return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX - 1,
+ spanY, direction, dragView, false, solution);
} else if (spanY > minSpanY) {
- return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
- direction, dragView, true, solution);
+ return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX,
+ spanY - 1, direction, dragView, true, solution);
}
solution.isSolution = false;
} else {
@@ -2443,7 +2450,7 @@
return solution;
}
- private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+ protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
@@ -2624,7 +2631,7 @@
return mItemPlacementDirty;
}
- private static class ItemConfiguration extends CellAndSpan {
+ static class ItemConfiguration extends CellAndSpan {
final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
final ArrayList<View> sortedViews = new ArrayList<>();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0070f95..086cf05 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -404,6 +404,7 @@
allAppsBorderSpacePx = new Point(
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
+ setupAllAppsStyle(context);
workspacePageIndicatorHeight = res.getDimensionPixelSize(
R.dimen.workspace_page_indicator_height);
@@ -810,9 +811,6 @@
int cellLayoutHorizontalPadding =
(cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
if (isTablet) {
- allAppsLeftRightPadding =
- res.getDimensionPixelSize(R.dimen.all_apps_bottom_sheet_horizontal_padding);
-
int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
+ (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
+ allAppsLeftRightPadding * 2;
@@ -823,6 +821,20 @@
}
}
+ private void setupAllAppsStyle(Context context) {
+ TypedArray allAppsStyle;
+ if (inv.allAppsStyle != INVALID_RESOURCE_HANDLE) {
+ allAppsStyle = context.obtainStyledAttributes(inv.allAppsStyle,
+ R.styleable.AllAppsStyle);
+ } else {
+ allAppsStyle = context.obtainStyledAttributes(R.style.AllAppsStyleDefault,
+ R.styleable.AllAppsStyle);
+ }
+ allAppsLeftRightPadding = allAppsStyle.getDimensionPixelSize(
+ R.styleable.AllAppsStyle_horizontalPadding, 0);
+ allAppsStyle.recycle();
+ }
+
/**
* Returns the amount of extra (or unused) vertical space.
*/
@@ -1806,7 +1818,7 @@
* Set the viewScaleProvider for the builder
*
* @param viewScaleProvider The viewScaleProvider to be set for the
- * DeviceProfile
+ * DeviceProfile
* @return This builder
*/
@NonNull
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index d97eac9..604c1b8 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -175,6 +175,7 @@
*/
public int numAllAppsColumns;
public int numDatabaseAllAppsColumns;
+ public @StyleRes int allAppsStyle;
/**
* Do not query directly. see {@link DeviceProfile#isScalableGrid}.
@@ -384,6 +385,8 @@
hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
hotseatQsbSpace = displayOption.hotseatQsbSpace;
+ allAppsStyle = closestProfile.allAppsStyle;
+
numAllAppsColumns = closestProfile.numAllAppsColumns;
numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
@@ -789,6 +792,7 @@
private final @StyleRes int folderStyle;
private final @StyleRes int cellStyle;
+ private final @StyleRes int allAppsStyle;
private final int numAllAppsColumns;
private final int numDatabaseAllAppsColumns;
private final int numHotseatIcons;
@@ -824,6 +828,8 @@
demoModeLayoutId = a.getResourceId(
R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
+ allAppsStyle = a.getResourceId(R.styleable.GridDisplayOption_allAppsStyle,
+ R.style.AllAppsStyleDefault);
numAllAppsColumns = a.getInt(
R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
numDatabaseAllAppsColumns = a.getInt(
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2f1f59c..41632f7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -44,6 +44,7 @@
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
import static com.android.launcher3.logging.StatsLogManager.EventEnum;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -138,6 +139,7 @@
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
+import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
@@ -730,7 +732,11 @@
}
onDeviceProfileInitiated();
- mCellPosMapper = CellPosMapper.DEFAULT;
+ if (FOLDABLE_SINGLE_PAGE.get() && mDeviceProfile.isTwoPanels) {
+ mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
+ } else {
+ mCellPosMapper = CellPosMapper.DEFAULT;
+ }
mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true,
mCellPosMapper, this);
return true;
@@ -1342,6 +1348,7 @@
// Setup Apps
mAppsView = findViewById(R.id.apps_view);
+ mAppsView.setAllAppsTransitionController(mAllAppsController);
// Setup Scrim
mScrimView = findViewById(R.id.scrim_view);
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
new file mode 100644
index 0000000..d671c7d
--- /dev/null
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
+import com.android.launcher3.util.CellAndSpan;
+import com.android.launcher3.util.GridOccupancy;
+
+import java.util.function.Supplier;
+
+/**
+ * CellLayout that simulates a split in the middle for use in foldable devices.
+ */
+public class MultipageCellLayout extends CellLayout {
+
+ private final Drawable mLeftBackground;
+ private final Drawable mRightBackground;
+
+ private View mSeam;
+
+ public MultipageCellLayout(Context context) {
+ this(context, null);
+ }
+
+ public MultipageCellLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MultipageCellLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mLeftBackground = getContext().getDrawable(R.drawable.bg_celllayout);
+ mLeftBackground.setCallback(this);
+ mLeftBackground.setAlpha(0);
+
+ mRightBackground = getContext().getDrawable(R.drawable.bg_celllayout);
+ mRightBackground.setCallback(this);
+ mRightBackground.setAlpha(0);
+
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+
+ mCountX = deviceProfile.inv.numColumns * 2;
+ mCountY = deviceProfile.inv.numRows;
+ mSeam = new View(getContext());
+ setGridSize(mCountX, mCountY);
+ }
+
+ @Override
+ ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
+ int spanX, int spanY) {
+ return simulateSeam(
+ () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
+ spanY));
+ }
+
+ @Override
+ protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+ int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+ ItemConfiguration solution) {
+ return simulateSeam(
+ () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
+ direction, dragView, decX, solution));
+ }
+
+ @Override
+ public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY,
+ View dragView) {
+ return simulateSeam(
+ () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView));
+ }
+
+ protected ItemConfiguration simulateSeam(Supplier<ItemConfiguration> f) {
+ CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mCountX / 2, 0, 1, mCountY);
+ lp.canReorder = false;
+ mCountX++;
+ mShortcutsAndWidgets.addViewInLayout(mSeam, lp);
+ GridOccupancy auxGrid = mOccupied;
+ mOccupied = createGridOccupancy();
+ mTmpOccupied = new GridOccupancy(mCountX, mCountY);
+
+ ItemConfiguration res = removeSeamFromSolution(f.get());
+
+ mCountX--;
+ mShortcutsAndWidgets.removeViewInLayout(mSeam);
+ mOccupied = auxGrid;
+ mTmpOccupied = new GridOccupancy(mCountX, mCountY);
+ return res;
+ }
+
+ private ItemConfiguration removeSeamFromSolution(ItemConfiguration solution) {
+ solution.map.forEach((view, cell) -> cell.cellX = cell.cellX > mCountX / 2
+ ? cell.cellX - 1 : cell.cellX);
+ solution.cellX = solution.cellX > mCountX / 2 ? solution.cellX - 1 : solution.cellX;
+ return solution;
+ }
+
+ GridOccupancy createGridOccupancy() {
+ GridOccupancy grid = new GridOccupancy(mCountX, mCountY);
+ for (int i = 0; i < mShortcutsAndWidgets.getChildCount(); i++) {
+ View view = mShortcutsAndWidgets.getChildAt(i);
+ CellLayoutLayoutParams lp = (CellLayoutLayoutParams) view.getLayoutParams();
+ int seamOffset = lp.getCellX() >= mCountX / 2 && lp.canReorder ? 1 : 0;
+ grid.markCells(lp.getCellX() + seamOffset, lp.getCellY(), lp.cellHSpan, lp.cellVSpan,
+ true);
+ }
+ return grid;
+ }
+
+ @Override
+ protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
+ int seamOffset = lp.getCellX() >= mCountX / 2 && lp.canReorder ? 1 : 0;
+ CellAndSpan c = new CellAndSpan(lp.getCellX() + seamOffset, lp.getCellY(), lp.cellHSpan,
+ lp.cellVSpan);
+ solution.add(child, c);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mLeftBackground.getAlpha() > 0) {
+ mLeftBackground.setState(mBackground.getState());
+ mLeftBackground.draw(canvas);
+ }
+ if (mRightBackground.getAlpha() > 0) {
+ mRightBackground.setState(mBackground.getState());
+ mRightBackground.draw(canvas);
+ }
+
+ super.onDraw(canvas);
+ }
+
+ @Override
+ protected void updateBgAlpha() {
+ mLeftBackground.setAlpha((int) (mSpringLoadedProgress * 255));
+ mRightBackground.setAlpha((int) (mSpringLoadedProgress * 255));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ Rect rect = mBackground.getBounds();
+ mLeftBackground.setBounds(rect.left, rect.top, rect.right / 2 - 20, rect.bottom);
+ mRightBackground.setBounds(rect.right / 2 + 20, rect.top, rect.right, rect.bottom);
+ }
+}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 5f39f7e..55b745b 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -107,6 +107,18 @@
}
}
+ /**
+ * Adds view to Layout a new position and it does not trigger a layout request.
+ * For more information check documentation for
+ * {@code ViewGroup#addViewInLayout(View, int, LayoutParams, boolean)}
+ *
+ * @param child view to be added
+ * @return true if the child was added, false otherwise
+ */
+ public boolean addViewInLayout(View child, LayoutParams layoutParams) {
+ return super.addViewInLayout(child, -1, layoutParams, true);
+ }
+
public void setupLp(View child) {
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
if (child instanceof NavigableAppWidgetHostView) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index df829f1..cfb8ca4 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -135,6 +135,7 @@
* The workspace is a wide area with a wallpaper and a finite number of pages.
* Each page contains a number of icons, folders or widgets the user can
* interact with. A workspace is meant to be used with a fixed width only.
+ *
* @param <T> Class that extends View and PageIndicator
*/
public class Workspace<T extends View & PageIndicator> extends PagedView<T>
@@ -142,12 +143,16 @@
DragController.DragListener, Insettable, StateHandler<LauncherState>,
WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
- /** The value that {@link #mTransitionProgress} must be greater than for
- * {@link #transitionStateShouldAllowDrop()} to return true. */
+ /**
+ * The value that {@link #mTransitionProgress} must be greater than for
+ * {@link #transitionStateShouldAllowDrop()} to return true.
+ */
private static final float ALLOW_DROP_TRANSITION_PROGRESS = 0.25f;
- /** The value that {@link #mTransitionProgress} must be greater than for
- * {@link #isFinishedSwitchingState()} ()} to return true. */
+ /**
+ * The value that {@link #mTransitionProgress} must be greater than for
+ * {@link #isFinishedSwitchingState()} ()} to return true.
+ */
private static final float FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS = 0.5f;
private static final float SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE = 0.15f;
@@ -161,16 +166,20 @@
private final int mAllAppsIconSize;
private LayoutTransition mLayoutTransition;
- @Thunk final WallpaperManager mWallpaperManager;
+ @Thunk
+ final WallpaperManager mWallpaperManager;
protected ShortcutAndWidgetContainer mDragSourceInternal;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- @Thunk public final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
+ @Thunk
+ public final IntSparseArrayMap<CellLayout> mWorkspaceScreens = new IntSparseArrayMap<>();
- @Thunk final IntArray mScreenOrder = new IntArray();
+ @Thunk
+ final IntArray mScreenOrder = new IntArray();
- @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
+ @Thunk
+ boolean mDeferRemoveExtraEmptyScreen = false;
/**
* CellInfo for the cell that is currently being dragged
@@ -180,14 +189,16 @@
/**
* Target drop area calculated during last acceptDrop call.
*/
- @Thunk int[] mTargetCell = new int[2];
+ @Thunk
+ int[] mTargetCell = new int[2];
private int mDragOverX = -1;
private int mDragOverY = -1;
/**
* The CellLayout that is currently being dragged over
*/
- @Thunk CellLayout mDragTargetLayout = null;
+ @Thunk
+ CellLayout mDragTargetLayout = null;
/**
* The CellLayout that we will show as highlighted
*/
@@ -198,13 +209,16 @@
*/
private CellLayout mDropToLayout = null;
- @Thunk final Launcher mLauncher;
- @Thunk DragController mDragController;
+ @Thunk
+ final Launcher mLauncher;
+ @Thunk
+ DragController mDragController;
protected final int[] mTempXY = new int[2];
private final float[] mTempFXY = new float[2];
private final Rect mTempRect = new Rect();
- @Thunk float[] mDragViewVisualCenter = new float[2];
+ @Thunk
+ float[] mDragViewVisualCenter = new float[2];
private SpringLoadedDragController mSpringLoadedDragController;
@@ -249,8 +263,10 @@
private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
private static final int DRAG_MODE_REORDER = 3;
protected int mDragMode = DRAG_MODE_NONE;
- @Thunk int mLastReorderX = -1;
- @Thunk int mLastReorderY = -1;
+ @Thunk
+ int mLastReorderX = -1;
+ @Thunk
+ int mLastReorderY = -1;
private SparseArray<Parcelable> mSavedStates;
private final IntArray mRestoredPages = new IntArray();
@@ -275,7 +291,7 @@
* Used to inflate the Workspace from XML.
*
* @param context The application's context.
- * @param attrs The attributes set containing the Workspace's customization values.
+ * @param attrs The attributes set containing the Workspace's customization values.
*/
public Workspace(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -284,8 +300,8 @@
/**
* Used to inflate the Workspace from XML.
*
- * @param context The application's context.
- * @param attrs The attributes set containing the Workspace's customization values.
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
* @param defStyle Unused.
*/
public Workspace(Context context, AttributeSet attrs, int defStyle) {
@@ -421,7 +437,9 @@
return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
}
- /** Returns the number of pages used for the wallpaper parallax. */
+ /**
+ * Returns the number of pages used for the wallpaper parallax.
+ */
public int getNumPagesForWallpaperParallax() {
return mWallpaperOffset.getNumPagesForWallpaperParallax();
}
@@ -483,7 +501,7 @@
}
private boolean isTwoPanelEnabled() {
- return mLauncher.mDeviceProfile.isTwoPanels;
+ return !FOLDABLE_SINGLE_PAGE.get() && mLauncher.mDeviceProfile.isTwoPanels;
}
@Override
@@ -553,6 +571,7 @@
void enableLayoutTransitions() {
setLayoutTransition(mLayoutTransition);
}
+
void disableLayoutTransitions() {
setLayoutTransition(null);
}
@@ -644,8 +663,15 @@
// Inflate the cell layout, but do not add it automatically so that we can get the newly
// created CellLayout.
- CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ CellLayout newScreen;
+ if (FOLDABLE_SINGLE_PAGE.get() && dp.isTwoPanels) {
+ newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
+ R.layout.workspace_screen_foldable, this, false /* attachToRoot */);
+ } else {
+ newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
+ }
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
@@ -783,17 +809,17 @@
* Empty page(s) from the end of mWorkspaceScreens will always be removed. The pages with
* ID = Workspace.EXTRA_EMPTY_SCREEN_IDS will be removed if there are other non-empty pages.
* If there are no more non-empty pages left, extra empty page(s) will either stay or get added.
- *
+ * <p>
* If stripEmptyScreens is true, all empty pages (not just the ones on the end) will be removed
* from the Workspace, and if there are no more pages left then extra empty page(s) will be
* added.
- *
+ * <p>
* The number of extra empty pages is equal to what getPanelCount() returns.
- *
+ * <p>
* After the method returns the possible pages are:
* stripEmptyScreens = true : [non-empty pages, extra empty page(s) alone]
* stripEmptyScreens = false : [non-empty pages, empty pages (not in the end),
- * extra empty page(s) alone]
+ * extra empty page(s) alone]
*/
public void removeExtraEmptyScreenDelayed(
int delay, boolean stripEmptyScreens, Runnable onComplete) {
@@ -844,11 +870,11 @@
}
/**
- * Commits the extra empty pages then returns the screen ids of those new screens.
- * Usually there's only one extra empty screen, but when two panel home is enabled we commit
- * two extra screens.
- *
- * Returns an empty IntSet in case we cannot commit any new screens.
+ * Commits the extra empty pages then returns the screen ids of those new screens.
+ * Usually there's only one extra empty screen, but when two panel home is enabled we commit
+ * two extra screens.
+ * <p>
+ * Returns an empty IntSet in case we cannot commit any new screens.
*/
public IntSet commitExtraEmptyScreens() {
if (mLauncher.isWorkspaceLoading()) {
@@ -869,7 +895,7 @@
mScreenOrder.removeValue(emptyScreenId);
int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
// Launcher database isn't aware of empty pages that are already bound, so we need to
// skip those IDs manually.
@@ -1058,8 +1084,10 @@
return mIsSwitchingState;
}
- /** This differs from isSwitchingState in that we take into account how far the transition
- * has completed. */
+ /**
+ * This differs from isSwitchingState in that we take into account how far the transition
+ * has completed.
+ */
public boolean isFinishedSwitchingState() {
return !mIsSwitchingState
|| (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
@@ -1387,7 +1415,9 @@
return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState();
}
- /** Returns whether a drag should be allowed to be started from the current workspace state. */
+ /**
+ * Returns whether a drag should be allowed to be started from the current workspace state.
+ */
public boolean workspaceIconsCanBeDragged() {
return mLauncher.getStateManager().getState().hasFlag(FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED);
}
@@ -1419,7 +1449,7 @@
// In overview mode, make sure that the two side pages are visible.
leftScreen = Utilities.boundToRange(getCurrentPage() - 1, 0, rightScreen);
rightScreen = Utilities.boundToRange(getCurrentPage() + 1,
- leftScreen, getPageCount() - 1);
+ leftScreen, getPageCount() - 1);
}
if (leftScreen == rightScreen) {
@@ -1621,7 +1651,7 @@
if (draggableView != null) {
draggableView.getSourceVisualDragBounds(dragRect);
dragLayerY += dragRect.top;
- dragVisualizeOffset = new Point(- halfPadding, halfPadding);
+ dragVisualizeOffset = new Point(-halfPadding, halfPadding);
}
@@ -1752,7 +1782,7 @@
}
boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
- float distance, boolean considerTimeout) {
+ float distance, boolean considerTimeout) {
if (distance > target.getFolderCreationRadius(targetCell)) return false;
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
return willCreateUserFolder(info, dropOverView, considerTimeout);
@@ -1788,12 +1818,13 @@
}
boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
- float distance) {
+ float distance) {
if (distance > target.getFolderCreationRadius(targetCell)) return false;
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
return willAddToExistingUserFolder(dragInfo, dropOverView);
}
+
boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
if (dropOverView != null) {
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
@@ -1894,7 +1925,7 @@
}
@Override
- public void prepareAccessibilityDrop() { }
+ public void prepareAccessibilityDrop() {}
@Override
public void onDrop(final DragObject d, DragOptions options) {
@@ -1912,8 +1943,8 @@
boolean resizeOnDrop = false;
Runnable onCompleteRunnable = null;
if (d.dragSource != this || mDragInfo == null) {
- final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
- (int) mDragViewVisualCenter[1] };
+ final int[] touchXY = new int[]{(int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1]};
onDropExternal(touchXY, dropTargetLayout, d);
} else {
final View cell = mDragInfo.cell;
@@ -2311,8 +2342,9 @@
/**
* Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
+ *
* @param layout either hotseat of a page in workspace
- * @param xy the point location in workspace co-ordinate space
+ * @param xy the point location in workspace co-ordinate space
*/
private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
if (mLauncher.isHotseatLayout(layout)) {
@@ -2379,7 +2411,7 @@
manageFolderFeedback(targetCellDistance, d);
boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
- mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
+ mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
item.spanY, child, mTargetCell);
manageReorderOnDragOver(d, targetCellDistance, nearestDropOccupied, minSpanX, minSpanY,
@@ -2400,11 +2432,15 @@
ItemInfo item = d.dragInfo;
final View child = (mDragInfo == null) ? null : mDragInfo.cell;
if (!nearestDropOccupied) {
+ int[] span = new int[2];
mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
- child, mTargetCell, new int[2], CellLayout.MODE_SHOW_REORDER_HINT);
- mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1],
- item.spanX, item.spanY, d);
+ child, mTargetCell, span, CellLayout.MODE_SHOW_REORDER_HINT);
+ mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1], span[0],
+ span[1], d);
+ nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
+ mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
+ item.spanY, child, mTargetCell);
} else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
&& (mLastReorderX != reorderX || mLastReorderY != reorderY)
&& targetCellDistance < mDragTargetLayout.getReorderRadius(mTargetCell, item.spanX,
@@ -2423,6 +2459,7 @@
mReorderAlarm.setAlarm(REORDER_TIMEOUT);
}
}
+
/**
* Updates {@link #mDragTargetLayout} and {@link #mDragOverlappingLayout}
* based on the DragObject's position.
@@ -2604,7 +2641,7 @@
final View child;
public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
- int spanY, DragObject dragObject, View child) {
+ int spanY, DragObject dragObject, View child) {
this.dragViewCenter = dragViewCenter;
this.minSpanX = minSpanX;
this.minSpanY = minSpanY;
@@ -2621,8 +2658,8 @@
mTargetCell);
mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
- (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
- child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+ child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
mDragTargetLayout.revertTempState();
@@ -2646,7 +2683,7 @@
* Drop an item that didn't originate on one of the workspace screens.
* It may have come from Launcher (e.g. from all apps or customize), or it may have
* come from another app altogether.
- *
+ * <p>
* NOTE: This can also be called when we are outside of a drag event, when we want
* to add an item to one of the workspace screens.
*/
@@ -2688,7 +2725,7 @@
mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
|| willAddToExistingUserFolder(
- d.dragInfo, cellLayout, mTargetCell, distance)) {
+ d.dragInfo, cellLayout, mTargetCell, distance)) {
findNearestVacantCell = false;
}
}
@@ -2923,7 +2960,7 @@
Drawable crossFadeDrawable = createWidgetDrawable(info, finalView);
dragView.crossFadeContent(crossFadeDrawable, (int) (duration * 0.8f));
} else if (isWidget && external) {
- scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
+ scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
}
DragLayer dragLayer = mLauncher.getDragLayer();
@@ -2962,6 +2999,7 @@
setScaleY(mStateTransitionAnimation.getFinalScale());
}
}
+
public void resetTransitionTransform() {
if (isSwitchingState()) {
setScaleX(mCurrentScale);
@@ -2973,7 +3011,6 @@
* Return the current CellInfo describing our current drag; this method exists
* so that Launcher can sync this object with the correct info when the activity is created/
* destroyed
- *
*/
public CellLayout.CellInfo getDragInfo() {
return mDragInfo;
@@ -2981,11 +3018,12 @@
/**
* Calculate the nearest cell where the given object would be dropped.
- *
+ * <p>
* pixelX and pixelY should be in the coordinate system of layout
*/
- @Thunk int[] findNearestArea(int pixelX, int pixelY,
- int spanX, int spanY, CellLayout layout, int[] recycle) {
+ @Thunk
+ int[] findNearestArea(int pixelX, int pixelY,
+ int spanX, int spanY, CellLayout layout, int[] recycle) {
return layout.findNearestAreaIgnoreOccupied(
pixelX, pixelY, spanX, spanY, recycle);
}
@@ -3003,7 +3041,7 @@
* Called at the end of a drag which originated on the workspace.
*/
public void onDropCompleted(final View target, final DragObject d,
- final boolean success) {
+ final boolean success) {
if (success) {
if (target != this && mDragInfo != null) {
removeWorkspaceItem(mDragInfo.cell);
@@ -3050,6 +3088,7 @@
/**
* Removed widget from workspace by appWidgetId
+ *
* @param appWidgetId
*/
public void removeWidget(int appWidgetId) {
@@ -3256,6 +3295,7 @@
/**
* Perform {param operator} over all the items in a given {param layout}.
+ *
* @return The first item that satisfies the operator or null.
*/
public View mapOverCellLayout(CellLayout layout, ItemOperator operator) {
@@ -3310,10 +3350,10 @@
/**
* Remove workspace icons & widget information related to items in matcher.
*
- * @param matcher the matcher generated by the caller.
+ * @param matcher the matcher generated by the caller.
*/
public void persistRemoveItemsByMatcher(Predicate<ItemInfo> matcher,
- @Nullable final String reason) {
+ @Nullable final String reason) {
mLauncher.getModelWriter().deleteItemsFromDatabase(matcher, reason);
removeItemsByMatcher(matcher);
}
@@ -3358,7 +3398,9 @@
return mOverlayShown;
}
- /** Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it. */
+ /**
+ * Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it.
+ */
public void moveToDefaultScreen() {
int page = DEFAULT_PAGE;
if (!workspaceInModalState() && getNextPage() != page) {
@@ -3458,7 +3500,7 @@
private boolean mRefreshPending;
DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
- LauncherWidgetHolder holder) {
+ LauncherWidgetHolder holder) {
mInfos = infos;
mWidgetHolder = holder;
mHandler = mLauncher.mHandler;
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 393baed..c3d24f9 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -155,6 +155,7 @@
private int mHeaderColor;
private int mBottomSheetBackgroundColor;
private int mTabsProtectionAlpha;
+ @Nullable private AllAppsTransitionController mAllAppsTransitionController;
public ActivityAllAppsContainerView(Context context) {
this(context, null);
@@ -292,6 +293,11 @@
animateToSearchState(goingToSearch, DEFAULT_SEARCH_TRANSITION_DURATION_MS);
}
+ public void setAllAppsTransitionController(
+ AllAppsTransitionController allAppsTransitionController) {
+ mAllAppsTransitionController = allAppsTransitionController;
+ }
+
private void animateToSearchState(boolean goingToSearch, long durationMs) {
if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
return;
@@ -299,6 +305,9 @@
if (goingToSearch) {
// Fade out the button to pause work apps.
mWorkManager.onActivePageChanged(SEARCH);
+ } else if (mAllAppsTransitionController != null) {
+ // If exiting search, revert predictive back scale on all apps
+ mAllAppsTransitionController.animateAllAppsToNoScale();
}
mSearchTransitionController.animateToSearchState(goingToSearch, durationMs,
/* onEndRunnable = */ () -> {
@@ -610,7 +619,13 @@
(int) (mSearchContainer.getAlpha() * 255));
}
- /** @return true if the search bar is at the bottom of the container (as opposed to the top). */
+ /**
+ * It is up to the search container view created by {@link #inflateSearchBox()} to use the
+ * floating search bar flag to move itself to the bottom of this container. This method checks
+ * if that had been done; otherwise the flag will be ignored.
+ *
+ * @return true if the search bar is at the bottom of the container (as opposed to the top).
+ **/
private boolean isSearchBarOnBottom() {
return FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()
&& ((RelativeLayout.LayoutParams) mSearchContainer.getLayoutParams()).getRule(
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 394a7d7..b618724 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -32,6 +32,7 @@
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.util.FloatProperty;
import android.view.HapticFeedbackConstants;
import android.view.View;
@@ -47,17 +48,21 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.ScrimView;
/**
@@ -78,6 +83,8 @@
private static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200;
private static final float NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.1f;
+ private static final float SWIPE_DRAG_COMMIT_THRESHOLD =
+ 1 - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -181,6 +188,7 @@
private boolean mIsTablet;
private boolean mHasScaleEffect;
+ private final VibratorWrapper mVibratorWrapper;
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
@@ -193,6 +201,7 @@
setShiftRange(dp.allAppsShiftRange);
mLauncher.addOnDeviceProfileChangeListener(this);
+ mVibratorWrapper = VibratorWrapper.INSTANCE.get(mLauncher.getApplicationContext());
}
public float getShiftRange() {
@@ -301,9 +310,21 @@
}
}
+ /** Animate all apps view to 1f scale. */
+ public void animateAllAppsToNoScale() {
+ mAllAppScale.animateToValue(1f)
+ .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
+ .start();
+ }
+
/**
* Creates an animation which updates the vertical transition progress and updates all the
* dependent UI using various animation events
+ *
+ * This method also dictates where along the progress the haptics should be played. As the user
+ * scrolls up from workspace or down from AllApps, a drag haptic is being played until the
+ * commit point where it plays a commit haptic. Where we play the haptics differs when going
+ * from workspace -> allApps and vice versa.
*/
@Override
public void setStateWithAnimation(LauncherState toState,
@@ -332,6 +353,20 @@
});
}
+ if(FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() && config.userControlled
+ && Utilities.ATLEAST_S) {
+ if (toState == ALL_APPS) {
+ builder.addOnFrameListener(
+ new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
+ SWIPE_DRAG_COMMIT_THRESHOLD, 1));
+ } else {
+ builder.addOnFrameListener(
+ new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
+ 0, SWIPE_DRAG_COMMIT_THRESHOLD));
+ }
+ builder.addEndListener(mVibratorWrapper::cancelVibrate);
+ }
+
float targetProgress = toState.getVerticalProgress(mLauncher);
if (Float.compare(mProgress, targetProgress) == 0) {
setAlphas(toState, config, builder);
@@ -349,7 +384,7 @@
setAlphas(toState, config, builder);
- if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) {
+ if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) && !(Utilities.ATLEAST_S)) {
mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
@@ -487,4 +522,45 @@
}
}
}
+
+ /**
+ * This VibrationAnimatorUpdateListener class takes in four parameters, a controller, start
+ * threshold, end threshold, and a Vibrator wrapper. We use the progress given by the controller
+ * as it gives an accurate progress that dictates where the vibrator should vibrate.
+ * Note: once the user begins a gesture and does the commit haptic, there should not be anymore
+ * haptics played for that gesture.
+ */
+ private static class VibrationAnimatorUpdateListener implements
+ ValueAnimator.AnimatorUpdateListener {
+ private final VibratorWrapper mVibratorWrapper;
+ private final AllAppsTransitionController mController;
+ private final float mStartThreshold;
+ private final float mEndThreshold;
+ private boolean mHasCommitted;
+
+ VibrationAnimatorUpdateListener(AllAppsTransitionController controller,
+ VibratorWrapper vibratorWrapper, float startThreshold,
+ float endThreshold) {
+ mController = controller;
+ mVibratorWrapper = vibratorWrapper;
+ mStartThreshold = startThreshold;
+ mEndThreshold = endThreshold;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (mHasCommitted) {
+ return;
+ }
+ float currentProgress =
+ AllAppsTransitionController.ALL_APPS_PROGRESS.get(mController);
+ if (currentProgress > mStartThreshold && currentProgress < mEndThreshold) {
+ mVibratorWrapper.vibrateForDragTexture();
+ } else if (!(currentProgress == 0 || currentProgress == 1)) {
+ // This check guards against committing at the location of the start of the gesture
+ mVibratorWrapper.vibrateForDragCommit();
+ mHasCommitted = true;
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a83a155..38af0de 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -320,8 +320,8 @@
"SCROLL_TOP_TO_RESET", true, "Bring up IME and focus on "
+ "input when scroll to top if 'Always show keyboard' is enabled or in prefix state");
- public static final BooleanFlag POPUP_MATERIAL_U = new DeviceFlag(
- "POPUP_MATERIAL_U", false, "Switch popup UX to use material U");
+ public static final BooleanFlag ENABLE_MATERIAL_U_POPUP = getDebugFlag(
+ "ENABLE_MATERIAL_U_POPUP", false, "Switch popup UX to use material U");
public static final BooleanFlag ENABLE_SEARCH_UNINSTALLED_APPS = new DeviceFlag(
"ENABLE_SEARCH_UNINSTALLED_APPS", false, "Search uninstalled app results.");
@@ -342,10 +342,6 @@
"ENABLE_DOWNLOAD_APP_UX_V3", false, "Updates the download app UX"
+ " to have better visuals, improve contrast, and color");
- public static final BooleanFlag ENABLE_TASKBAR_REVISED_THRESHOLDS = getDebugFlag(
- "ENABLE_TASKBAR_REVISED_THRESHOLDS", true,
- "Uses revised thresholds for transient taskbar.");
-
public static final BooleanFlag FORCE_PERSISTENT_TASKBAR = getDebugFlag(
"FORCE_PERSISTENT_TASKBAR", false, "Forces taskbar to be persistent, even in gesture"
+ " nav mode and when transient taskbar is enabled.");
@@ -378,6 +374,8 @@
"ENABLE_LAUNCH_FROM_STAGED_APP", true,
"Enable the ability to tap a staged app during split select to launch it in full screen"
);
+ public static final BooleanFlag ENABLE_HAPTICS_ALL_APPS = getDebugFlag(
+ "ENABLE_HAPTICS_ALL_APPS", false, "Enables haptics opening/closing All apps");
public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(
"ENABLE_FORCED_MONO_ICON", false,
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index bfd0e1b..a53751f 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -129,10 +129,7 @@
Interpolators.clampToProgress(
Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1f),
ALL_APPS_STATE_TRANSITION_ATOMIC, 1f);
- public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL =
- Interpolators.clampToProgress(
- Interpolators.mapToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f),
- ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
+ public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL = LINEAR;
// --------
diff --git a/src/com/android/launcher3/util/GridOccupancy.java b/src/com/android/launcher3/util/GridOccupancy.java
index 1301460..43e486c 100644
--- a/src/com/android/launcher3/util/GridOccupancy.java
+++ b/src/com/android/launcher3/util/GridOccupancy.java
@@ -81,4 +81,16 @@
public void clear() {
markCells(0, 0, mCountX, mCountY, false);
}
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder("Grid: \n");
+ for (int y = 0; y < mCountY; y++) {
+ for (int x = 0; x < mCountX; x++) {
+ s.append(cells[x][y] ? 1 : 0).append(" ");
+ }
+ s.append("\n");
+ }
+ return s.toString();
+ }
}
diff --git a/src/com/android/launcher3/util/ResourceHelper.kt b/src/com/android/launcher3/util/ResourceHelper.kt
new file mode 100644
index 0000000..0ca7888
--- /dev/null
+++ b/src/com/android/launcher3/util/ResourceHelper.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.content.res.XmlResourceParser
+import android.util.AttributeSet
+import kotlin.IntArray
+
+/**
+ * This class is a helper that can be subclassed in tests to provide a way to parse attributes
+ * correctly.
+ */
+open class ResourceHelper(private val context: Context, private val specsFileId: Int) {
+ open fun getXml(): XmlResourceParser {
+ return context.resources.getXml(specsFileId)
+ }
+
+ open fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
+ return context.obtainStyledAttributes(attrs, styleId)
+ }
+}
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index 932bcfc..ceba0db 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -17,7 +17,6 @@
import static android.os.VibrationEffect.createPredefined;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
-
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -28,12 +27,17 @@
import android.database.ContentObserver;
import android.media.AudioAttributes;
import android.os.Build;
+import android.os.SystemClock;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.anim.PendingAnimation;
+
+import java.util.function.Consumer;
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
@@ -52,6 +56,21 @@
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
+ private static final float DRAG_TEXTURE_SCALE = 0.03f;
+ private static final float DRAG_COMMIT_SCALE = 0.5f;
+ private static final float DRAG_BUMP_SCALE = 0.4f;
+ private static final int DRAG_TEXTURE_EFFECT_SIZE = 200;
+
+ @Nullable
+ private final VibrationEffect mDragEffect;
+ @Nullable
+ private final VibrationEffect mCommitEffect;
+ @Nullable
+ private final VibrationEffect mBumpEffect;
+
+ private long mLastDragTime;
+ private final int mThresholdUntilNextDragCallMillis;
+
/**
* Haptic when entering overview.
*/
@@ -62,7 +81,7 @@
private boolean mIsHapticFeedbackEnabled;
- public VibratorWrapper(Context context) {
+ private VibratorWrapper(Context context) {
mVibrator = context.getSystemService(Vibrator.class);
mHasVibrator = mVibrator.hasVibrator();
if (mHasVibrator) {
@@ -75,12 +94,88 @@
}
};
resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
- false /* notifyForDescendents */, observer);
+ false /* notifyForDescendants */, observer);
} else {
mIsHapticFeedbackEnabled = false;
}
+
+ if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK)) {
+
+ // Drag texture, Commit, and Bump should only be used for premium phones.
+ // Before using these haptics make sure check if the device can use it
+ VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
+ for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
+ dragEffect.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
+ }
+ mDragEffect = dragEffect.compose();
+ mCommitEffect = VibrationEffect.startComposition().addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_TICK, DRAG_COMMIT_SCALE).compose();
+ mBumpEffect = VibrationEffect.startComposition().addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK, DRAG_BUMP_SCALE).compose();
+ int primitiveDuration = mVibrator.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0];
+
+ mThresholdUntilNextDragCallMillis =
+ DRAG_TEXTURE_EFFECT_SIZE * primitiveDuration + 100;
+ } else {
+ mDragEffect = null;
+ mCommitEffect = null;
+ mBumpEffect = null;
+ mThresholdUntilNextDragCallMillis = 0;
+ }
}
+ /**
+ * This is called when the user swipes to/from all apps. This is meant to be used in between
+ * long animation progresses so that it gives a dragging texture effect. For a better
+ * experience, this should be used in combination with vibrateForDragCommit().
+ */
+ public void vibrateForDragTexture() {
+ if (mDragEffect == null) {
+ return;
+ }
+ long currentTime = SystemClock.elapsedRealtime();
+ long elapsedTimeSinceDrag = currentTime - mLastDragTime;
+ if (elapsedTimeSinceDrag >= mThresholdUntilNextDragCallMillis) {
+ vibrate(mDragEffect);
+ mLastDragTime = currentTime;
+ }
+ }
+
+ /**
+ * This is used when user reaches the commit threshold when swiping to/from from all apps.
+ */
+ public void vibrateForDragCommit() {
+ if (mCommitEffect != null) {
+ vibrate(mCommitEffect);
+ }
+ // resetting dragTexture timestamp to be able to play dragTexture again
+ mLastDragTime = 0;
+ }
+
+ /**
+ * The bump haptic is used to be called at the end of a swipe and only if it the gesture is a
+ * FLING going to/from all apps. Client can just call this method elsewhere just for the
+ * effect.
+ */
+ public void vibrateForDragBump() {
+ if (mBumpEffect != null) {
+ vibrate(mBumpEffect);
+ }
+ }
+
+ /**
+ * This should be used to cancel a haptic in case where the haptic shouldn't be vibrating. For
+ * example, when no animation is happening but a vibrator happens to be vibrating still. Need
+ * boolean parameter for {@link PendingAnimation#addEndListener(Consumer)}.
+ */
+ public void cancelVibrate(boolean unused) {
+ UI_HELPER_EXECUTOR.execute(mVibrator::cancel);
+ // reset dragTexture timestamp to be able to play dragTexture again whenever cancelled
+ mLastDragTime = 0;
+ }
private boolean isHapticFeedbackEnabled(ContentResolver resolver) {
return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1;
}
diff --git a/src/com/android/launcher3/widget/util/WidgetSizes.java b/src/com/android/launcher3/widget/util/WidgetSizes.java
index fb2d63b..601c1b5 100644
--- a/src/com/android/launcher3/widget/util/WidgetSizes.java
+++ b/src/com/android/launcher3/widget/util/WidgetSizes.java
@@ -24,6 +24,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
+import android.util.Log;
import android.util.Size;
import android.util.SizeF;
@@ -162,6 +163,8 @@
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, rect.right);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, rect.bottom);
options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
+ Log.d("b/267448330", "provider: " + provider + ", paddedSizes: " + paddedSizes
+ + ", getMinMaxSizes: " + rect);
return options;
}
diff --git a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
new file mode 100644
index 0000000..0f6e1b0
--- /dev/null
+++ b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.workspace
+
+import android.content.res.TypedArray
+import android.content.res.XmlResourceParser
+import android.util.AttributeSet
+import android.util.Log
+import android.util.TypedValue
+import android.util.Xml
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+import java.io.IOException
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+private const val TAG = "WorkspaceSpecs"
+
+class WorkspaceSpecs(resourceHelper: ResourceHelper) {
+ object XmlTags {
+ const val WORKSPACE_SPECS = "workspaceSpecs"
+
+ const val WORKSPACE_SPEC = "workspaceSpec"
+ const val START_PADDING = "startPadding"
+ const val END_PADDING = "endPadding"
+ const val GUTTER = "gutter"
+ const val CELL_SIZE = "cellSize"
+ }
+
+ val workspaceHeightSpecList = mutableListOf<WorkspaceSpec>()
+ val workspaceWidthSpecList = mutableListOf<WorkspaceSpec>()
+
+ init {
+ try {
+ val parser: XmlResourceParser = resourceHelper.getXml()
+ val depth = parser.depth
+ var type: Int
+ while (
+ (parser.next().also { type = it } != XmlPullParser.END_TAG ||
+ parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
+ ) {
+ if (type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPECS == parser.name) {
+ val displayDepth = parser.depth
+ while (
+ (parser.next().also { type = it } != XmlPullParser.END_TAG ||
+ parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
+ ) {
+ if (
+ type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPEC == parser.name
+ ) {
+ val attrs =
+ resourceHelper.obtainStyledAttributes(
+ Xml.asAttributeSet(parser),
+ R.styleable.WorkspaceSpec
+ )
+ val maxAvailableSize =
+ attrs.getDimensionPixelSize(
+ R.styleable.WorkspaceSpec_maxAvailableSize,
+ 0
+ )
+ val specType =
+ WorkspaceSpec.SpecType.values()[
+ attrs.getInt(
+ R.styleable.WorkspaceSpec_specType,
+ WorkspaceSpec.SpecType.HEIGHT.ordinal
+ )
+ ]
+ attrs.recycle()
+
+ var startPadding: SizeSpec? = null
+ var endPadding: SizeSpec? = null
+ var gutter: SizeSpec? = null
+ var cellSize: SizeSpec? = null
+
+ val limitDepth = parser.depth
+ while (
+ (parser.next().also { type = it } != XmlPullParser.END_TAG ||
+ parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
+ ) {
+ val attr: AttributeSet = Xml.asAttributeSet(parser)
+ if (type == XmlPullParser.START_TAG) {
+ when (parser.name) {
+ XmlTags.START_PADDING -> {
+ startPadding = SizeSpec(resourceHelper, attr)
+ }
+ XmlTags.END_PADDING -> {
+ endPadding = SizeSpec(resourceHelper, attr)
+ }
+ XmlTags.GUTTER -> {
+ gutter = SizeSpec(resourceHelper, attr)
+ }
+ XmlTags.CELL_SIZE -> {
+ cellSize = SizeSpec(resourceHelper, attr)
+ }
+ }
+ }
+ }
+
+ if (
+ startPadding == null ||
+ endPadding == null ||
+ gutter == null ||
+ cellSize == null
+ ) {
+ throw IllegalStateException(
+ "All attributes in workspaceSpec must be defined"
+ )
+ }
+
+ val workspaceSpec =
+ WorkspaceSpec(
+ maxAvailableSize,
+ specType,
+ startPadding,
+ endPadding,
+ gutter,
+ cellSize
+ )
+ if (workspaceSpec.isValid()) {
+ if (workspaceSpec.specType == WorkspaceSpec.SpecType.HEIGHT)
+ workspaceHeightSpecList.add(workspaceSpec)
+ else workspaceWidthSpecList.add(workspaceSpec)
+ } else {
+ throw IllegalStateException("Invalid workspaceSpec found.")
+ }
+ }
+ }
+
+ if (workspaceWidthSpecList.isEmpty() || workspaceHeightSpecList.isEmpty()) {
+ throw IllegalStateException(
+ "WorkspaceSpecs is incomplete - " +
+ "height list size = ${workspaceHeightSpecList.size}; " +
+ "width list size = ${workspaceWidthSpecList.size}."
+ )
+ }
+ }
+ }
+ parser.close()
+ } catch (e: Exception) {
+ when (e) {
+ is IOException,
+ is XmlPullParserException -> {
+ throw RuntimeException("Failure parsing workspaces specs file.", e)
+ }
+ else -> throw e
+ }
+ }
+ }
+}
+
+data class WorkspaceSpec(
+ val maxAvailableSize: Int,
+ val specType: SpecType,
+ val startPadding: SizeSpec,
+ val endPadding: SizeSpec,
+ val gutter: SizeSpec,
+ val cellSize: SizeSpec
+) {
+
+ enum class SpecType {
+ HEIGHT,
+ WIDTH
+ }
+
+ fun isValid(): Boolean {
+ if (maxAvailableSize <= 0) {
+ Log.e(TAG, "WorkspaceSpec#isValid - maxAvailableSize <= 0")
+ return false
+ }
+
+ // All specs need to be individually valid
+ if (!allSpecsAreValid()) {
+ Log.e(TAG, "WorkspaceSpec#isValid - !allSpecsAreValid()")
+ return false
+ }
+
+ return true
+ }
+
+ private fun allSpecsAreValid(): Boolean =
+ startPadding.isValid() && endPadding.isValid() && gutter.isValid() && cellSize.isValid()
+}
+
+class SizeSpec(resourceHelper: ResourceHelper, attrs: AttributeSet) {
+ val fixedSize: Float
+ val ofAvailableSpace: Float
+ val ofRemainderSpace: Float
+
+ init {
+ val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SpecSize)
+
+ fixedSize = getValue(styledAttrs, R.styleable.SpecSize_fixedSize)
+ ofAvailableSpace = getValue(styledAttrs, R.styleable.SpecSize_ofAvailableSpace)
+ ofRemainderSpace = getValue(styledAttrs, R.styleable.SpecSize_ofRemainderSpace)
+
+ styledAttrs.recycle()
+ }
+
+ private fun getValue(a: TypedArray, index: Int): Float {
+ if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
+ return a.getDimensionPixelSize(index, 0).toFloat()
+ } else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
+ return a.getFloat(index, 0f)
+ }
+ return 0f
+ }
+
+ fun isValid(): Boolean {
+ // All attributes are empty
+ if (fixedSize <= 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
+ Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
+ return false
+ }
+
+ // More than one attribute is filled
+ val attrCount =
+ (if (fixedSize > 0) 1 else 0) +
+ (if (ofAvailableSpace > 0) 1 else 0) +
+ (if (ofRemainderSpace > 0) 1 else 0)
+ if (attrCount > 1) {
+ Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled")
+ return false
+ }
+
+ // Values should be between 0 and 1
+ if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) {
+ Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1")
+ return false
+ }
+
+ return true
+ }
+
+ override fun toString(): String {
+ return "SizeSpec(fixedSize=$fixedSize, ofAvailableSpace=$ofAvailableSpace, " +
+ "ofRemainderSpace=$ofRemainderSpace)"
+ }
+}
diff --git a/tests/res/values/attrs.xml b/tests/res/values/attrs.xml
new file mode 100644
index 0000000..2310d9e
--- /dev/null
+++ b/tests/res/values/attrs.xml
@@ -0,0 +1,35 @@
+<?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.
+*/
+-->
+
+<!-- Attributes have to be copied to test for correct parsing of files -->
+<resources>
+ <!-- Responsive grids attributes -->
+ <declare-styleable name="WorkspaceSpec">
+ <attr name="specType" format="integer">
+ <enum name="height" value="0" />
+ <enum name="width" value="1" />
+ </attr>
+ <attr name="maxAvailableSize" format="dimension" />
+ </declare-styleable>
+
+ <declare-styleable name="SpecSize">
+ <attr name="fixedSize" format="dimension" />
+ <attr name="ofAvailableSpace" format="float" />
+ <attr name="ofRemainderSpace" format="float" />
+ </declare-styleable>
+
+</resources>
diff --git a/tests/res/xml/invalid_workspace_file_case_1.xml b/tests/res/xml/invalid_workspace_file_case_1.xml
new file mode 100644
index 0000000..0be704b
--- /dev/null
+++ b/tests/res/xml/invalid_workspace_file_case_1.xml
@@ -0,0 +1,56 @@
+<?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.
+ -->
+
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="648dp">
+ <!-- missing startPadding -->
+ <endPadding
+ launcher:ofAvailableSpace="0.05" />
+ <gutter
+ launcher:fixedSize="16dp" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </workspaceSpec>
+
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0306" />
+ <endPadding
+ launcher:ofAvailableSpace="0.068" />
+ <gutter
+ launcher:fixedSize="16dp" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </workspaceSpec>
+
+ <!-- Width spec is always the same -->
+ <workspaceSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <endPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <gutter
+ launcher:ofRemainderSpace="0.11425509" />
+ <cellSize
+ launcher:fixedSize="120dp" />
+ </workspaceSpec>
+</workspaceSpecs>
diff --git a/tests/res/xml/invalid_workspace_file_case_2.xml b/tests/res/xml/invalid_workspace_file_case_2.xml
new file mode 100644
index 0000000..5a37d97
--- /dev/null
+++ b/tests/res/xml/invalid_workspace_file_case_2.xml
@@ -0,0 +1,59 @@
+<?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.
+ -->
+
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="648dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0125" />
+ <endPadding
+ launcher:ofAvailableSpace="0.05" />
+ <!-- more than 1 value in one tag -->
+ <gutter
+ launcher:ofAvailableSpace="0.0125"
+ launcher:fixedSize="16dp" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </workspaceSpec>
+
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0306" />
+ <endPadding
+ launcher:ofAvailableSpace="0.068" />
+ <gutter
+ launcher:fixedSize="16dp" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </workspaceSpec>
+
+ <!-- Width spec is always the same -->
+ <workspaceSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <endPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <gutter
+ launcher:ofRemainderSpace="0.11425509" />
+ <cellSize
+ launcher:fixedSize="120dp" />
+ </workspaceSpec>
+</workspaceSpecs>
diff --git a/tests/res/xml/invalid_workspace_file_case_3.xml b/tests/res/xml/invalid_workspace_file_case_3.xml
new file mode 100644
index 0000000..3e68edb
--- /dev/null
+++ b/tests/res/xml/invalid_workspace_file_case_3.xml
@@ -0,0 +1,58 @@
+<?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.
+ -->
+
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="648dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0125" />
+ <endPadding
+ launcher:ofAvailableSpace="0.05" />
+ <gutter
+ launcher:fixedSize="16dp" />
+ <!-- value bigger than 1 -->
+ <cellSize
+ launcher:ofRemainderSpace="1.001" />
+ </workspaceSpec>
+
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0306" />
+ <endPadding
+ launcher:ofAvailableSpace="0.068" />
+ <gutter
+ launcher:fixedSize="16dp" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </workspaceSpec>
+
+ <!-- Width spec is always the same -->
+ <workspaceSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <endPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <gutter
+ launcher:ofRemainderSpace="0.11425509" />
+ <cellSize
+ launcher:fixedSize="120dp" />
+ </workspaceSpec>
+</workspaceSpecs>
diff --git a/tests/res/xml/valid_workspace_file.xml b/tests/res/xml/valid_workspace_file.xml
new file mode 100644
index 0000000..91a3e48
--- /dev/null
+++ b/tests/res/xml/valid_workspace_file.xml
@@ -0,0 +1,57 @@
+<?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.
+ -->
+
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="648dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0125" />
+ <endPadding
+ launcher:ofAvailableSpace="0.05" />
+ <gutter
+ launcher:fixedSize="16dp" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </workspaceSpec>
+
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0306" />
+ <endPadding
+ launcher:ofAvailableSpace="0.068" />
+ <gutter
+ launcher:fixedSize="16dp" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </workspaceSpec>
+
+ <!-- Width spec is always the same -->
+ <workspaceSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <endPadding
+ launcher:ofRemainderSpace="0.21436227" />
+ <gutter
+ launcher:ofRemainderSpace="0.11425509" />
+ <cellSize
+ launcher:fixedSize="120dp" />
+ </workspaceSpec>
+</workspaceSpecs>
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
new file mode 100644
index 0000000..dcc669b
--- /dev/null
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.Surface
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.WindowBounds
+import com.android.launcher3.util.window.CachedDisplayInfo
+import com.android.launcher3.util.window.WindowManagerProxy
+import kotlin.math.max
+import kotlin.math.min
+import org.junit.After
+import org.junit.Before
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
+ * a real device spec.
+ *
+ * For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
+ */
+abstract class AbstractDeviceProfileTest {
+ protected var context: Context? = null
+ protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
+ private var displayController: DisplayController = mock(DisplayController::class.java)
+ private var windowManagerProxy: WindowManagerProxy = mock(WindowManagerProxy::class.java)
+ private lateinit var originalDisplayController: DisplayController
+ private lateinit var originalWindowManagerProxy: WindowManagerProxy
+
+ @Before
+ fun setUp() {
+ val appContext: Context = ApplicationProvider.getApplicationContext()
+ originalWindowManagerProxy = WindowManagerProxy.INSTANCE.get(appContext)
+ originalDisplayController = DisplayController.INSTANCE.get(appContext)
+ WindowManagerProxy.INSTANCE.initializeForTesting(windowManagerProxy)
+ DisplayController.INSTANCE.initializeForTesting(displayController)
+ }
+
+ @After
+ fun tearDown() {
+ WindowManagerProxy.INSTANCE.initializeForTesting(originalWindowManagerProxy)
+ DisplayController.INSTANCE.initializeForTesting(originalDisplayController)
+ }
+
+ class DeviceSpec(
+ val naturalSize: Pair<Int, Int>,
+ val densityDpi: Int,
+ val statusBarNaturalPx: Int,
+ val statusBarRotatedPx: Int,
+ val gesturePx: Int,
+ val cutoutPx: Int
+ )
+
+ open val deviceSpecs =
+ mapOf(
+ "phone" to
+ DeviceSpec(
+ Pair(1080, 2400),
+ densityDpi = 420,
+ statusBarNaturalPx = 118,
+ statusBarRotatedPx = 74,
+ gesturePx = 63,
+ cutoutPx = 118
+ ),
+ "tablet" to
+ DeviceSpec(
+ Pair(2560, 1600),
+ densityDpi = 320,
+ statusBarNaturalPx = 104,
+ statusBarRotatedPx = 104,
+ gesturePx = 0,
+ cutoutPx = 0
+ ),
+ )
+
+ protected fun initializeVarsForPhone(
+ deviceSpec: DeviceSpec,
+ isGestureMode: Boolean = true,
+ isVerticalBar: Boolean = false
+ ) {
+ val (naturalX, naturalY) = deviceSpec.naturalSize
+ val windowsBounds = phoneWindowsBounds(deviceSpec, isGestureMode, naturalX, naturalY)
+ val displayInfo =
+ CachedDisplayInfo(Point(naturalX, naturalY), Surface.ROTATION_0, Rect(0, 0, 0, 0))
+ val perDisplayBoundsCache = mapOf(displayInfo to windowsBounds)
+
+ initializeCommonVars(
+ perDisplayBoundsCache,
+ displayInfo,
+ rotation = if (isVerticalBar) Surface.ROTATION_90 else Surface.ROTATION_0,
+ isGestureMode,
+ densityDpi = deviceSpec.densityDpi
+ )
+ }
+
+ protected fun initializeVarsForTablet(
+ deviceSpec: DeviceSpec,
+ isLandscape: Boolean = false,
+ isGestureMode: Boolean = true
+ ) {
+ val (naturalX, naturalY) = deviceSpec.naturalSize
+ val windowsBounds = tabletWindowsBounds(deviceSpec, naturalX, naturalY)
+ val displayInfo =
+ CachedDisplayInfo(Point(naturalX, naturalY), Surface.ROTATION_0, Rect(0, 0, 0, 0))
+ val perDisplayBoundsCache = mapOf(displayInfo to windowsBounds)
+
+ initializeCommonVars(
+ perDisplayBoundsCache,
+ displayInfo,
+ rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
+ isGestureMode,
+ densityDpi = deviceSpec.densityDpi
+ )
+ }
+
+ protected fun initializeVarsForTwoPanel(
+ deviceTabletSpec: DeviceSpec,
+ deviceSpec: DeviceSpec,
+ isLandscape: Boolean = false,
+ isGestureMode: Boolean = true
+ ) {
+ val (tabletNaturalX, tabletNaturalY) = deviceTabletSpec.naturalSize
+ val tabletWindowsBounds =
+ tabletWindowsBounds(deviceTabletSpec, tabletNaturalX, tabletNaturalY)
+ val tabletDisplayInfo =
+ CachedDisplayInfo(
+ Point(tabletNaturalX, tabletNaturalY),
+ Surface.ROTATION_0,
+ Rect(0, 0, 0, 0)
+ )
+
+ val (phoneNaturalX, phoneNaturalY) = deviceSpec.naturalSize
+ val phoneWindowsBounds =
+ phoneWindowsBounds(deviceSpec, isGestureMode, phoneNaturalX, phoneNaturalY)
+ val phoneDisplayInfo =
+ CachedDisplayInfo(
+ Point(phoneNaturalX, phoneNaturalY),
+ Surface.ROTATION_0,
+ Rect(0, 0, 0, 0)
+ )
+
+ val perDisplayBoundsCache =
+ mapOf(tabletDisplayInfo to tabletWindowsBounds, phoneDisplayInfo to phoneWindowsBounds)
+
+ initializeCommonVars(
+ perDisplayBoundsCache,
+ tabletDisplayInfo,
+ rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
+ isGestureMode,
+ densityDpi = deviceTabletSpec.densityDpi
+ )
+ }
+
+ private fun phoneWindowsBounds(
+ deviceSpec: DeviceSpec,
+ isGestureMode: Boolean,
+ naturalX: Int,
+ naturalY: Int
+ ): Array<WindowBounds> {
+ val buttonsNavHeight = Utilities.dpToPx(48f, deviceSpec.densityDpi)
+
+ val rotation0Insets =
+ Rect(
+ 0,
+ max(deviceSpec.statusBarNaturalPx, deviceSpec.cutoutPx),
+ 0,
+ if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight
+ )
+ val rotation90Insets =
+ Rect(
+ deviceSpec.cutoutPx,
+ deviceSpec.statusBarRotatedPx,
+ if (isGestureMode) 0 else buttonsNavHeight,
+ if (isGestureMode) deviceSpec.gesturePx else 0
+ )
+ val rotation180Insets =
+ Rect(
+ 0,
+ deviceSpec.statusBarNaturalPx,
+ 0,
+ max(
+ if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight,
+ deviceSpec.cutoutPx
+ )
+ )
+ val rotation270Insets =
+ Rect(
+ if (isGestureMode) 0 else buttonsNavHeight,
+ deviceSpec.statusBarRotatedPx,
+ deviceSpec.cutoutPx,
+ if (isGestureMode) deviceSpec.gesturePx else 0
+ )
+
+ return arrayOf(
+ WindowBounds(Rect(0, 0, naturalX, naturalY), rotation0Insets, Surface.ROTATION_0),
+ WindowBounds(Rect(0, 0, naturalY, naturalX), rotation90Insets, Surface.ROTATION_90),
+ WindowBounds(Rect(0, 0, naturalX, naturalY), rotation180Insets, Surface.ROTATION_180),
+ WindowBounds(Rect(0, 0, naturalY, naturalX), rotation270Insets, Surface.ROTATION_270)
+ )
+ }
+
+ private fun tabletWindowsBounds(
+ deviceSpec: DeviceSpec,
+ naturalX: Int,
+ naturalY: Int
+ ): Array<WindowBounds> {
+ val naturalInsets = Rect(0, deviceSpec.statusBarNaturalPx, 0, 0)
+ val rotatedInsets = Rect(0, deviceSpec.statusBarRotatedPx, 0, 0)
+
+ return arrayOf(
+ WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_0),
+ WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_90),
+ WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_180),
+ WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_270)
+ )
+ }
+
+ private fun initializeCommonVars(
+ perDisplayBoundsCache: Map<CachedDisplayInfo, Array<WindowBounds>>,
+ displayInfo: CachedDisplayInfo,
+ rotation: Int,
+ isGestureMode: Boolean = true,
+ densityDpi: Int
+ ) {
+ val windowsBounds = perDisplayBoundsCache[displayInfo]!!
+ val realBounds = windowsBounds[rotation]
+ whenever(windowManagerProxy.getDisplayInfo(ArgumentMatchers.any())).thenReturn(displayInfo)
+ whenever(windowManagerProxy.getRealBounds(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ .thenReturn(realBounds)
+ whenever(windowManagerProxy.getRotation(ArgumentMatchers.any())).thenReturn(rotation)
+ whenever(windowManagerProxy.getNavigationMode(ArgumentMatchers.any()))
+ .thenReturn(
+ if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
+ )
+
+ val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val config =
+ Configuration(runningContext.resources.configuration).apply {
+ this.densityDpi = densityDpi
+ screenWidthDp = (realBounds.bounds.width() / density).toInt()
+ screenHeightDp = (realBounds.bounds.height() / density).toInt()
+ smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
+ }
+ context = runningContext.createConfigurationContext(config)
+
+ val info = DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache)
+ whenever(displayController.info).thenReturn(info)
+ whenever(displayController.isTransientTaskbar).thenReturn(isGestureMode)
+ }
+}
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
rename to tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 0aecfb2..a5f33c0 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -31,7 +31,13 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
-abstract class DeviceProfileBaseTest {
+/**
+ * This is an abstract class for DeviceProfile tests that don't need the real Context and mock an
+ * InvariantDeviceProfile instead of creating one based on real values.
+ *
+ * For an implementation that creates InvariantDeviceProfile, use [AbstractDeviceProfileTest]
+ */
+abstract class FakeInvariantDeviceProfileTest {
protected var context: Context? = null
protected var inv: InvariantDeviceProfile? = null
@@ -120,6 +126,7 @@
horizontalMargin = FloatArray(4) { 22f }
+ allAppsStyle = R.style.AllAppsStyleDefault
allAppsCellSize =
listOf(
PointF(80f, 104f),
@@ -198,6 +205,7 @@
horizontalMargin = floatArrayOf(54f, 120f, 54f, 54f)
+ allAppsStyle = R.style.AllAppsStyleDefault
allAppsCellSize =
listOf(
PointF(96f, 142f),
@@ -277,6 +285,7 @@
horizontalMargin = floatArrayOf(21.5f, 21.5f, 22.5f, 30.5f)
+ allAppsStyle = R.style.AllAppsStyleDefault
allAppsCellSize =
listOf(PointF(0f, 0f), PointF(0f, 0f), PointF(68f, 104f), PointF(80f, 104f))
.toTypedArray()
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index a85fa3a..ca269a9 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -33,6 +33,7 @@
import com.android.launcher3.provider.LauncherDbUtils
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.LauncherModelHelper.*
+import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -750,20 +751,14 @@
}
private fun enableNewMigrationLogic(srcGridSize: String) {
- context
- .getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE)
- .edit()
- .putBoolean(FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.key, true)
- .commit()
LauncherPrefs.get(context).putSync(WORKSPACE_SIZE.to(srcGridSize))
+ TestUtil.overrideBooleanFlagValue(context,
+ FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC, true);
FeatureFlags.initialize(context)
}
private fun disableNewMigrationLogic() {
- context
- .getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE)
- .edit()
- .putBoolean(FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.key, false)
- .commit()
+ TestUtil.overrideBooleanFlagValue(context,
+ FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC, false);
}
}
diff --git a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
index 951f5f8..2a27487 100644
--- a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
@@ -18,7 +18,7 @@
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.launcher3.DeviceProfileBaseTest
+import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.WindowBounds
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -26,7 +26,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
+class HotseatWidthCalculationTest : FakeInvariantDeviceProfileTest() {
/**
* This is a case when after setting the hotseat, the space needs to be recalculated but it
diff --git a/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java b/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
index 082e243..33a7f5c 100644
--- a/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
+++ b/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
@@ -38,6 +38,7 @@
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.TestUtil;
import org.junit.After;
import org.junit.Ignore;
@@ -278,10 +279,8 @@
private void setDragNDropFlag(Boolean status) {
Context context = new LauncherModelHelper().sandboxContext;
- context.getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE).edit()
- .putBoolean(FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.key, status)
- .commit();
- FeatureFlags.initialize(context);
+ TestUtil.overrideBooleanFlagValue(
+ context, FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN, status);
startSecondaryDisplayActivity();
}
}
diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
new file mode 100644
index 0000000..fb03fe1
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import com.android.launcher3.R
+import com.android.launcher3.tests.R as TestR
+import kotlin.IntArray
+
+class TestResourceHelper(private val context: Context, private val specsFileId: Int) :
+ ResourceHelper(context, specsFileId) {
+ override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
+ var clone = styleId.clone()
+ if (styleId == R.styleable.SpecSize) clone = TestR.styleable.SpecSize
+ else if (styleId == R.styleable.WorkspaceSpec) clone = TestR.styleable.WorkspaceSpec
+ return context.obtainStyledAttributes(attrs, clone)
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java
index d7c6c4f..38de4c3 100644
--- a/tests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -19,14 +19,18 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.InstrumentationRegistry.getTargetContext;
+import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
+import androidx.annotation.VisibleForTesting;
import androidx.test.uiautomator.UiDevice;
+import com.android.launcher3.config.FeatureFlags;
+
import org.junit.Assert;
import java.io.FileOutputStream;
@@ -68,6 +72,18 @@
}
}
+ @VisibleForTesting
+ // Override feature flag, mainly to be used ONLY in tests
+ public static void overrideBooleanFlagValue(
+ Context context, FeatureFlags.BooleanFlag flagToOverride,
+ boolean bool) {
+ context.getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(flagToOverride.key, bool)
+ .commit();
+ FeatureFlags.initialize(context);
+ }
+
public static void uninstallDummyApp() throws IOException {
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
"pm uninstall " + DUMMY_PACKAGE);
diff --git a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
new file mode 100644
index 0000000..0fd8a54
--- /dev/null
+++ b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.workspace
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+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 WorkspaceSpecsTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpecs["phone"]!!)
+ }
+
+ @Test
+ fun parseValidFile() {
+ val workspaceSpecs =
+ WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(2)
+ assertThat(workspaceSpecs.workspaceHeightSpecList[0].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=1701, " +
+ "specType=HEIGHT, " +
+ "startPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0125, " +
+ "ofRemainderSpace=0.0), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.05, " +
+ "ofRemainderSpace=0.0), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0), " +
+ "cellSize=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.2)" +
+ ")"
+ )
+ assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=26247, " +
+ "specType=HEIGHT, " +
+ "startPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0306, " +
+ "ofRemainderSpace=0.0), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.068, " +
+ "ofRemainderSpace=0.0), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0), " +
+ "cellSize=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.2)" +
+ ")"
+ )
+ assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
+ assertThat(workspaceSpecs.workspaceWidthSpecList[0].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=26247, " +
+ "specType=WIDTH, " +
+ "startPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.21436226), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.21436226), " +
+ "gutter=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.11425509), " +
+ "cellSize=SizeSpec(fixedSize=315.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0)" +
+ ")"
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_missingTag_throwsError() {
+ WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_1))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
+ WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_2))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_valueBiggerThan1_throwsError() {
+ WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_3))
+ }
+}