[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: ae62fdcad1 -s ours am: db1564dc30 -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/24916778
Change-Id: If1984c83b5d81c5530fe8e845b820808b1e9b5ee
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index a7edf2a..316f9c0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -153,7 +153,8 @@
"androidx.cardview_cardview",
"com.google.android.material_material",
"iconloader_base",
- "view_capture"
+ "view_capture",
+ "animationlib"
],
manifest: "AndroidManifest-common.xml",
sdk_version: "current",
@@ -172,6 +173,7 @@
static_libs: [
"Launcher3ResLib",
"launcher-testing-shared",
+ "animationlib"
],
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
diff --git a/OWNERS b/OWNERS
index b684460..7834396 100644
--- a/OWNERS
+++ b/OWNERS
@@ -13,6 +13,8 @@
winsonc@google.com
jonmiranda@google.com
alexchau@google.com
+patmanning@google.com
+tsuharesu@google.com
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index e5b2c67..29b24b7 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -25,14 +25,13 @@
import android.os.Binder;
import android.os.Bundle;
import android.system.Os;
-import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -62,7 +61,6 @@
public void onActivityCreated(Activity activity, Bundle bundle) {
sActivities.put(activity, true);
++sActivitiesCreatedCount;
- Log.d(TestProtocol.FLAKY_ACTIVITY_COUNT, "onActivityCreated", new Exception());
}
@Override
@@ -188,13 +186,29 @@
return response;
}
+ case TestProtocol.REQUEST_REINITIALIZE_DATA: {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ MODEL_EXECUTOR.execute(() -> {
+ LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+ model.getModelDbController().createEmptyDB();
+ MAIN_EXECUTOR.execute(model::forceReload);
+ });
+ return response;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
case TestProtocol.REQUEST_CLEAR_DATA: {
final long identity = Binder.clearCallingIdentity();
try {
- LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- MAIN_EXECUTOR.submit(() ->
- LauncherAppState.getInstance(mContext).getModel().forceReload());
+ MODEL_EXECUTOR.execute(() -> {
+ LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+ model.getModelDbController().createEmptyDB();
+ model.getModelDbController().clearEmptyDbFlag();
+ MAIN_EXECUTOR.execute(model::forceReload);
+ });
return response;
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 63ea20c..f8b08f8 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -135,7 +135,7 @@
}
}
-// Next value 51
+// Next value 52
enum Attribute {
option allow_alias = true;
@@ -187,6 +187,7 @@
ALL_APPS_SEARCH_RESULT_SYSTEM_POINTER = 42;
ALL_APPS_SEARCH_RESULT_EDUCARD = 43;
ALL_APPS_SEARCH_RESULT_LOCATION = 50;
+ ALL_APPS_SEARCH_RESULT_TEXT_HEADER = 51;
// Result sources
DATA_SOURCE_APPSEARCH_APP_PREVIEW = 45;
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index f5a8253..638ce27 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -42,5 +42,6 @@
"tests/src/com/android/quickstep/NavigationModeSwitchRule.java",
"tests/src/com/android/quickstep/AbstractQuickStepTest.java",
"tests/src/com/android/quickstep/TaplTestsQuickstep.java",
+ "tests/src/com/android/quickstep/TaplTestsSplitscreen.java",
]
}
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 7d7054f..c6e2d8c 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -20,7 +20,6 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher3">
- <uses-sdk android:targetSdkVersion="33" android:minSdkVersion="26"/>
<!--
Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
Refer comments around specific entries on how to extend individual components.
diff --git a/quickstep/protos_overrides/launcher_atom_extension.proto b/quickstep/protos_overrides/launcher_atom_extension.proto
index f5a277b..b3df353 100644
--- a/quickstep/protos_overrides/launcher_atom_extension.proto
+++ b/quickstep/protos_overrides/launcher_atom_extension.proto
@@ -61,6 +61,9 @@
// User entered by tapping on QSB bar on homescreen.
QSB = 2;
+
+ // User entered by swiping up from overview (using Rocket Gesture).
+ OVERVIEW = 3;
}
}
}
diff --git a/res/interpolator/standard_decelerate.xml b/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
similarity index 65%
rename from res/interpolator/standard_decelerate.xml
rename to quickstep/res/drawable/bg_bubble_dismiss_circle.xml
index 579f4f5..b793eec 100644
--- a/res/interpolator/standard_decelerate.xml
+++ b/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ 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.
@@ -15,8 +15,13 @@
~ limitations under the License.
-->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0"
- android:controlY1="0"
- android:controlX2="0"
- android:controlY2="1"/>
\ No newline at end of file
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <stroke
+ android:width="2dp"
+ android:color="@android:color/system_accent1_600" />
+
+ <solid android:color="@android:color/system_accent1_600" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_floating_desktop_select.xml b/quickstep/res/drawable/bg_floating_desktop_select.xml
new file mode 100644
index 0000000..d7df338
--- /dev/null
+++ b/quickstep/res/drawable/bg_floating_desktop_select.xml
@@ -0,0 +1,23 @@
+<?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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+
+ <corners android:radius="@dimen/rounded_button_radius" />
+ <solid android:color="?androidprv:attr/materialColorPrimaryContainer" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_bubble_dismiss_white.xml b/quickstep/res/drawable/ic_bubble_dismiss_white.xml
new file mode 100644
index 0000000..b15111b
--- /dev/null
+++ b/quickstep/res/drawable/ic_bubble_dismiss_white.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+ android:fillColor="@android:color/system_neutral1_50"/>
+</vector>
diff --git a/quickstep/res/drawable/taskbar_divider_button.xml b/quickstep/res/drawable/taskbar_divider_button.xml
new file mode 100644
index 0000000..cb116cf
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_divider_button.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="52dp"
+ android:height="52dp"
+ android:viewportHeight="52"
+ android:viewportWidth="52">
+ <group>
+ <path
+ android:fillColor="@color/taskbar_divider_background"
+ android:pathData="M26,11L26,41"
+ android:strokeColor="@color/taskbar_divider_background"
+ android:strokeLineCap="round"
+ android:strokeWidth="2" />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/interpolator/three_point_fast_out_extra_slow_in.xml b/quickstep/res/interpolator/three_point_fast_out_extra_slow_in.xml
deleted file mode 100644
index 70c4231..0000000
--- a/quickstep/res/interpolator/three_point_fast_out_extra_slow_in.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2021, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1"/>
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index 4e67629..69e1574 100644
--- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
@@ -23,7 +23,7 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
- launcher:borderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
index 225b4cd..672440f 100644
--- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
@@ -13,155 +13,163 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/GestureTutorialActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
- android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
- android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
- android:background="?androidprv:attr/materialColorSurfaceContainer">
+ android:background="?androidprv:attr/materialColorSurfaceContainer"
+ android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_home_button"
- android:layout_width="0dp"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
- android:layout_marginBottom="24dp"
- android:background="@drawable/gesture_tutorial_menu_home_button_background"
- android:clipToOutline="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
+ android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
+ android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
+ android:clipToPadding="false">
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_back_button">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_home_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
- android:id="@+id/gesture_tutorial_menu_home_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/home_gesture_tutorial_title"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_home_button"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/gesture_tutorial_menu_home_button_background"
+ android:clipToOutline="true"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_back_button">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_home_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
+ android:id="@+id/gesture_tutorial_menu_home_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/home_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_back_button"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/gesture_tutorial_menu_back_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_home_button"
+ app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_overview_button">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_back_step_shape"
+ android:layout_marginBottom="@dimen/gesture_tutorial_menu_back_shape_bottom_margin"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
+ android:id="@+id/gesture_tutorial_menu_back_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_overview_button"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/gesture_tutorial_menu_overview_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_back_button"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_overview_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
+ android:id="@+id/gesture_tutorial_menu_overview_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/overview_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+
+ app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
+
+ <Button
+ style="@style/TextAppearance.GestureTutorial.ButtonLabel"
+ android:id="@+id/gesture_tutorial_menu_done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="40dp"
+ android:layout_marginVertical="16dp"
+ android:text="@string/gesture_tutorial_action_button_label"
+ android:background="@drawable/gesture_tutorial_action_button_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:stateListAnimator="@null"
+
+ app:layout_constraintTop_toBottomOf="@id/guideline"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_back_button"
- android:layout_width="0dp"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
- android:layout_marginBottom="24dp"
- android:background="@drawable/gesture_tutorial_menu_back_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_home_button"
- app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_overview_button">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_back_step_shape"
- android:layout_marginBottom="@dimen/gesture_tutorial_menu_back_shape_bottom_margin"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
- android:id="@+id/gesture_tutorial_menu_back_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/back_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_overview_button"
- android:layout_width="0dp"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginBottom="24dp"
- android:background="@drawable/gesture_tutorial_menu_overview_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_back_button"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_overview_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
- android:id="@+id/gesture_tutorial_menu_overview_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/overview_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/guideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
-
- app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
-
- <Button
- style="@style/TextAppearance.GestureTutorial.ButtonLabel"
- android:id="@+id/gesture_tutorial_menu_done_button"
- android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginVertical="16dp"
- android:text="@string/gesture_tutorial_action_button_label"
- android:background="@drawable/gesture_tutorial_action_button_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
- android:stateListAnimator="@null"
-
- app:layout_constraintTop_toBottomOf="@id/guideline"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 2c312a7..685a151 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -41,6 +41,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
+ android:fitsSystemWindows="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -95,7 +96,6 @@
style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="24dp"
android:text="@string/allset_hint"
android:textSize="@dimen/allset_page_swipe_up_text_size"
android:gravity="center_horizontal"
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index bfeb82d..f0ea09c 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -15,6 +15,7 @@
-->
<com.android.launcher3.LauncherRootView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
diff --git a/quickstep/res/layout/floating_desktop_app_select.xml b/quickstep/res/layout/floating_desktop_app_select.xml
new file mode 100644
index 0000000..375fc44
--- /dev/null
+++ b/quickstep/res/layout/floating_desktop_app_select.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.
+-->
+
+<com.android.quickstep.views.DesktopAppSelectView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/desktop_mode_floating_app_select_height"
+ android:layout_gravity="top|center_horizontal"
+ android:background="@drawable/bg_floating_desktop_select"
+ android:elevation="@dimen/desktop_mode_floating_app_select_elevation"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/desktop_app_select_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/desktop_mode_floating_app_select_text_margin"
+ android:layout_marginStart="@dimen/desktop_mode_floating_app_select_margin"
+ android:drawablePadding="@dimen/desktop_mode_floating_app_select_text_margin"
+ android:drawableStart="@drawable/ic_desktop"
+ android:drawableTint="?androidprv:attr/materialColorOnPrimaryContainer"
+ android:fontFamily="google-sans-medium"
+ android:gravity="center_vertical"
+ android:text="@string/desktop_select_app_toast"
+ android:textColor="?androidprv:attr/materialColorOnPrimaryContainer"
+ android:textSize="@dimen/desktop_mode_floating_app_select_text_size" />
+
+ <Button
+ android:id="@+id/close_button"
+ style="@android:style/Widget.DeviceDefault.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/desktop_mode_floating_app_select_margin"
+ android:minWidth="0dp"
+ android:fontFamily="google-sans-medium"
+ android:text="@string/desktop_button_close_app_toast"
+ android:textAllCaps="false"
+ android:textColor="?androidprv:attr/materialColorPrimary"
+ android:textSize="@dimen/desktop_mode_floating_app_select_text_size" />
+
+</com.android.quickstep.views.DesktopAppSelectView>
diff --git a/quickstep/res/layout/gesture_tutorial_activity.xml b/quickstep/res/layout/gesture_tutorial_activity.xml
index 0e763ec..82caedf 100644
--- a/quickstep/res/layout/gesture_tutorial_activity.xml
+++ b/quickstep/res/layout/gesture_tutorial_activity.xml
@@ -13,80 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/gesture_tutorial_fragment_container"
android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <FrameLayout
- android:id="@+id/gesture_tutorial_fragment_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent" />
-
- <RelativeLayout
- android:id="@+id/rotation_prompt"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?attr/surfaceHome"
- android:visibility="gone"
-
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/background"
- android:layout_width="300dp"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:background="@drawable/rotate_prompt_bg"
- android:padding="24dp">
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/rotate_tutorial_warning"
-
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintEnd_toEndOf="parent" />
-
- <TextView
- android:id="@+id/rotate_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:lineSpacingExtra="2sp"
- android:text="@string/gesture_tutorial_rotation_prompt_title"
- android:textAppearance="@style/rotate_prompt_title"
-
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/icon"
- app:layout_constraintEnd_toEndOf="parent" />
-
- <TextView
- android:id="@+id/rotate_screen_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:gravity="center"
- android:text="@string/gesture_tutorial_rotation_prompt"
- android:textAppearance="@style/rotate_prompt_subtitle"
-
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/rotate_title"
- app:layout_constraintEnd_toEndOf="parent" />
-
- </androidx.constraintlayout.widget.ConstraintLayout>
- </RelativeLayout>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+ android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_rotation_prompt.xml b/quickstep/res/layout/gesture_tutorial_rotation_prompt.xml
new file mode 100644
index 0000000..b41a96d
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_rotation_prompt.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/surfaceHome">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/background"
+ android:layout_width="300dp"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:background="@drawable/rotate_prompt_bg"
+ android:padding="24dp"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/rotate_tutorial_warning"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <TextView
+ android:id="@+id/rotate_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:lineSpacingExtra="2sp"
+ android:text="@string/gesture_tutorial_rotation_prompt_title"
+ android:textAppearance="@style/rotate_prompt_title"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:gravity="center"
+ android:text="@string/gesture_tutorial_rotation_prompt"
+ android:textAppearance="@style/rotate_prompt_subtitle"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/rotate_title"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml
index cf78b1b..c8ee6e9 100644
--- a/quickstep/res/layout/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml
@@ -13,154 +13,161 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/GestureTutorialActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
- android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
- android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
android:background="?androidprv:attr/materialColorSurfaceContainer"
- android:clipToPadding="false">
+ android:fitsSystemWindows="true">
<androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_home_button"
android:layout_width="match_parent"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:background="@drawable/gesture_tutorial_menu_home_button_background"
- android:clipToOutline="true"
- app:layout_constraintVertical_chainStyle="packed"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
+ android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
+ android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
+ android:clipToPadding="false">
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_back_button"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_home_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
- android:id="@+id/gesture_tutorial_menu_home_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/home_gesture_tutorial_title"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_home_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:background="@drawable/gesture_tutorial_menu_home_button_background"
+ android:clipToOutline="true"
+ app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_back_button"
app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_home_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
+ android:id="@+id/gesture_tutorial_menu_home_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/home_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_back_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
+ android:background="@drawable/gesture_tutorial_menu_back_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_home_button"
+ app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_overview_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_back_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
+ android:id="@+id/gesture_tutorial_menu_back_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/gesture_tutorial_menu_overview_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+ android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
+ android:background="@drawable/gesture_tutorial_menu_overview_button_background"
+ android:clipToOutline="true"
+
+ app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_back_button"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_overview_step_shape"
+ android:scaleType="fitXY"
+ android:adjustViewBounds="true"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
+ android:id="@+id/gesture_tutorial_menu_overview_button_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/overview_gesture_tutorial_title"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+
+ app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
+
+ <Button
+ style="@style/TextAppearance.GestureTutorial.ButtonLabel"
+ android:id="@+id/gesture_tutorial_menu_done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="40dp"
+ android:layout_marginVertical="16dp"
+ android:text="@string/gesture_tutorial_action_button_label"
+ android:background="@drawable/gesture_tutorial_action_button_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
+ android:stateListAnimator="@null"
+
+ app:layout_constraintTop_toBottomOf="@id/guideline"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_back_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
- android:background="@drawable/gesture_tutorial_menu_back_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_home_button"
- app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_overview_button"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_back_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
- android:id="@+id/gesture_tutorial_menu_back_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/back_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/gesture_tutorial_menu_overview_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/gesture_tutorial_menu_button_height"
- android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
- android:background="@drawable/gesture_tutorial_menu_overview_button_background"
- android:clipToOutline="true"
-
- app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_back_button"
- app:layout_constraintBottom_toTopOf="@id/guideline"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent">
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/gesture_tutorial_overview_step_shape"
- android:scaleType="fitXY"
- android:adjustViewBounds="true"
-
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
- android:id="@+id/gesture_tutorial_menu_overview_button_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/overview_gesture_tutorial_title"
-
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- </androidx.constraintlayout.widget.ConstraintLayout>
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/guideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
-
- app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
-
- <Button
- style="@style/TextAppearance.GestureTutorial.ButtonLabel"
- android:id="@+id/gesture_tutorial_menu_done_button"
- android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginVertical="16dp"
- android:text="@string/gesture_tutorial_action_button_label"
- android:background="@drawable/gesture_tutorial_action_button_background"
- android:backgroundTint="?androidprv:attr/materialColorPrimary"
- android:stateListAnimator="@null"
-
- app:layout_constraintTop_toBottomOf="@id/guideline"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/keyboard_quick_switch_overview.xml b/quickstep/res/layout/keyboard_quick_switch_overview.xml
index e7b1f23..4a9b023 100644
--- a/quickstep/res/layout/keyboard_quick_switch_overview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_overview.xml
@@ -22,7 +22,7 @@
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:clipToOutline="true"
android:importantForAccessibility="yes"
- launcher:borderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index 4d213fa..6ed3c6e 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
@@ -23,7 +23,7 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
- launcher:borderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml
index 91fb05c..c663bf4 100644
--- a/quickstep/res/layout/split_instructions_view.xml
+++ b/quickstep/res/layout/split_instructions_view.xml
@@ -24,12 +24,15 @@
android:paddingTop="@dimen/split_instructions_vertical_padding"
android:paddingBottom="@dimen/split_instructions_vertical_padding"
android:elevation="@dimen/split_instructions_elevation"
- android:visibility="gone">
+ android:visibility="gone"
+ android:importantForAccessibility="yes">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/split_instructions_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:gravity="center"
android:textColor="?androidprv:attr/textColorOnAccent"
+ android:drawableEnd="@drawable/ic_split_exit"
+ android:drawablePadding="@dimen/split_instructions_drawable_padding"
android:text="@string/toast_split_select_app" />
</com.android.quickstep.views.SplitInstructionsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 4865aef..29c9992 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -24,7 +24,8 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
- launcher:borderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
+ launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index fd82c66..06f4d06 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -25,7 +25,8 @@
android:clipToOutline="true"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
- launcher:borderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
+ launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<View
android:id="@+id/background"
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index c9fa9c0..75ff626 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -29,7 +29,8 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
- launcher:borderColor="?androidprv:attr/materialColorOutline">
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
+ launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
diff --git a/quickstep/res/layout/taskbar_all_apps.xml b/quickstep/res/layout/taskbar_all_apps.xml
index 976cd9e..e234165 100644
--- a/quickstep/res/layout/taskbar_all_apps.xml
+++ b/quickstep/res/layout/taskbar_all_apps.xml
@@ -14,18 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView
+<com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:accessibilityPaneTitle="@string/all_apps_label">
-
- <com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView
- android:id="@+id/apps_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="true"
- android:clipToPadding="false"
- android:focusable="false"
- android:saveEnabled="false" />
-</com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView>
+ android:clipChildren="true"
+ android:clipToPadding="false"
+ android:focusable="false"
+ android:saveEnabled="false" />
diff --git a/quickstep/res/layout/taskbar_all_apps_sheet.xml b/quickstep/res/layout/taskbar_all_apps_sheet.xml
new file mode 100644
index 0000000..a1d5fa6
--- /dev/null
+++ b/quickstep/res/layout/taskbar_all_apps_sheet.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:accessibilityPaneTitle="@string/all_apps_label">
+
+ <include
+ android:id="@+id/apps_view"
+ layout="@layout/taskbar_all_apps" />
+</com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView>
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
index 73f3811..0a92fa9 100644
--- a/quickstep/res/layout/taskbar_divider.xml
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -13,16 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout
+<com.android.launcher3.views.IconButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/taskbar_icon_min_touch_size"
android:layout_height="@dimen/taskbar_icon_min_touch_size"
android:contentDescription="@string/taskbar_divider_a11y_title"
- android:backgroundTint="@android:color/transparent">
-
- <View
- android:layout_height="32dp"
- android:layout_width="2dp"
- android:layout_gravity="center"
- android:background="@drawable/taskbar_divider_bg" />
-</FrameLayout>
\ No newline at end of file
+ android:backgroundTint="@android:color/transparent" />
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_divider_popup_menu.xml b/quickstep/res/layout/taskbar_divider_popup_menu.xml
index 195443e..00e47c9 100644
--- a/quickstep/res/layout/taskbar_divider_popup_menu.xml
+++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml
@@ -32,7 +32,7 @@
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
- android:background="@drawable/top_rounded_popup_ripple"
+ android:background="@drawable/rounded_popup_ripple"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:theme="@style/PopupItem">
@@ -59,40 +59,4 @@
android:text="@string/always_show_taskbar" />
</LinearLayout>
-
- <LinearLayout
- android:id="@+id/navigation_mode_switch_option"
- android:layout_width="match_parent"
- android:layout_height="52dp"
- android:layout_gravity="center_vertical"
- android:elevation="2dp"
- android:clickable="true"
- android:focusable="true"
- android:background="@drawable/bottom_rounded_popup_ripple"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:paddingEnd="10dp"
- android:paddingStart="10dp"
- android:theme="@style/PopupItem">
-
- <View
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_margin="4dp"
- android:background="@drawable/ic_touch"
- android:backgroundTint="?android:attr/textColorPrimary" />
-
- <com.android.launcher3.BubbleTextView
- style="@style/BaseIcon"
- android:id="@+id/change_navigation_mode_text"
- android:gravity="start|center_vertical"
- android:textAlignment="viewStart"
- android:paddingStart="12dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:textSize="14sp"
- android:textColor="?android:attr/textColorPrimary"
- android:text="@string/change_navigation_mode" />
-
- </LinearLayout>
</com.android.launcher3.taskbar.TaskbarDividerPopupView>
\ No newline at end of file
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index bf4b811..0890a4e 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -44,12 +44,12 @@
android:layout_height="@dimen/bubblebar_size"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/transient_taskbar_bottom_margin"
- android:layout_marginBottom="@dimen/transient_taskbar_bottom_margin"
android:paddingEnd="@dimen/taskbar_icon_spacing"
android:paddingStart="@dimen/taskbar_icon_spacing"
android:visibility="gone"
android:gravity="center"
android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation"
/>
<FrameLayout
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index f7a1d1a..b589e32 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Skermkiekie"</string>
<string name="action_split" msgid="2098009717623550676">"Verdeel"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tik op ’n ander app om verdeelde skerm te gebruik"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Verlaat verdeeldeskermkeuse"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Kies nog ’n app as jy verdeelde skerm wil gebruik"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Jou organisasie laat nie hierdie program toe nie"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Slaan navigasietutoriaal oor?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Skuif na regs onder"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Wys nog # app.}other{Wys nog # apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> en <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Voeg nou app by werkskerm"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Kanselleer"</string>
</resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 9b30d67..a678b3f 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ቅጽበታዊ ገፅ ዕይታ"</string>
<string name="action_split" msgid="2098009717623550676">"ክፈል"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"የተከፈለ ማያ ገጽን ለመጠቀም ሌላ መተግበሪያ መታ ያድርጉ"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ከተከፈለ ማያ ገፅ ምርጫ ይውጡ"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"የተከፈለ ማያ ገጽን ለመቀበል ሌላ መተግበሪያ ይምረጡ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ይህ ድርጊት በመተግበሪያው ወይም በእርስዎ ድርጅት አይፈቀድም"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"የአሰሳ አጋዥ ሥልጠናን ይዝለሉ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ወደ ታች/ቀኝ ይውሰዱ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ተጨማሪ # መተግበሪያ አሳይ።}one{ተጨማሪ # መተግበሪያ አሳይ።}other{ተጨማሪ # መተግበሪያዎች አሳይ።}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> እና <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"መተግበሪያን ወደ ዴስክቶፕ በማከል ላይ"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ይቅር"</string>
</resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index f58a75e..6d7effc 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"لقطة شاشة"</string>
<string name="action_split" msgid="2098009717623550676">"تقسيم"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"انقر على تطبيق آخر لاستخدام وضع تقسيم الشاشة."</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"الخروج من وضع تقسيم الشاشة"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"اختَر تطبيقًا آخر لاستخدام وضع تقسيم الشاشة."</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"لا يسمح التطبيق أو لا تسمح مؤسستك بهذا الإجراء."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"هل تريد تخطي الدليل التوجيهي للتنقّل؟"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"الانتقال إلى يسار الشاشة أو أسفلها"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{إظهار تطبيق واحد آخر}zero{إظهار # تطبيق آخر}two{إظهار تطبيقَين آخرَين}few{إظهار # تطبيقات أخرى}many{إظهار # تطبيقًا آخر}other{إظهار # تطبيق آخر}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"\"<xliff:g id="APP_NAME_1">%1$s</xliff:g>\" و\"<xliff:g id="APP_NAME_2">%2$s</xliff:g>\""</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"إضافة تطبيق إلى سطح المكتب"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"إلغاء"</string>
</resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 05d1b99..a68aee5 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"স্ক্ৰীনশ্বট"</string>
<string name="action_split" msgid="2098009717623550676">"বিভাজন কৰক"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ অন্য এটা এপত টিপক"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"বিভাজিত স্ক্ৰীনৰ বাছনিৰ পৰা বাহিৰ হওক"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰিবলৈ অন্য এটা এপ্ বাছক"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"এপ্টোৱে অথবা আপোনাৰ প্ৰতিষ্ঠানে এই কাৰ্যটোৰ অনুমতি নিদিয়ে"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"নেভিগেশ্বনৰ টিউট’ৰিয়েল এৰিব বিচাৰে নেকি?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"তলৰ সোঁফাললৈ নিয়ক"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{আৰু # টা এপ্ দেখুৱাওক।}one{আৰু # টা এপ্ দেখুৱাওক।}other{আৰু # টা এপ্ দেখুৱাওক।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> আৰু <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ডেস্কটপত এপ্ যোগ দি থকা হৈছে"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"বাতিল কৰক"</string>
</resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index 3e5bb6f..9fff2d8 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Skrinşot"</string>
<string name="action_split" msgid="2098009717623550676">"Ayırın"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Bölünmüş ekran üçün başqa tətbiqə toxunun"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Bölünmüş ekran seçimindən çıxın"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Bölünmüş ekrandan istifadə üçün başqa tətbiq seçin"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Bu əməliyyata tətbiq və ya təşkilatınız tərəfindən icazə verilmir"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Naviqasiya dərsliyi ötürülsün?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Aşağı/sağa köçürün"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Daha # tətbiqi göstərin.}other{Daha # tətbiqi göstərin.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> və <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Tətbiqin masaüstünə əlavə edilməsi"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Ləğv edin"</string>
</resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index e6d87f8..9a7aa4b 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Snimak ekrana"</string>
<string name="action_split" msgid="2098009717623550676">"Podeli"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Dodirnite drugu aplikaciju za podeljeni ekran"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Izlazak iz biranja podeljenog ekrana"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Odaberite drugu aplikaciju za podeljeni ekran"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Aplikacija ili organizacija ne dozvoljavaju ovu radnju"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Želite da preskočite vodič za kretanje?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premesti dole desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju.}one{Prikaži još # aplikaciju.}few{Prikaži još # aplikacije.}other{Prikaži još # aplikacija.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodaje se aplikacija na radnu povrršinu"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Otkaži"</string>
</resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index c7358ff..ca62108 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Здымак экрана"</string>
<string name="action_split" msgid="2098009717623550676">"Падзелены экран"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Каб падзяліць экран, націсніце на іншую праграму"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Выйсці з рэжыму падзеленага экрана"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Каб падзяліць экран, выберыце іншую праграму"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Гэта дзеянне не дазволена праграмай ці вашай арганізацыяй"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Прапусціць дапаможнік па навігацыі?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перамясціць уніз/управа"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Паказаць ячшэ # праграму.}one{Паказаць ячшэ # праграму.}few{Паказаць ячшэ # праграмы.}many{Паказаць ячшэ # праграм.}other{Паказаць ячшэ # праграмы.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> і <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Дадаванне праграмы на камп\'ютар"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Скасаваць"</string>
</resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index dbb878d..72e731a 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Екранна снимка"</string>
<string name="action_split" msgid="2098009717623550676">"Разделяне на екрана"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Докоснете друго прил., за да ползвате разд. екран"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Изход от избора на разделен екран"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"За разделен екран изберете още едно приложение"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Това действие не е разрешено от приложението или организацията ви"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Пропускане на урока за навигиране?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Преместване долу/вдясно"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показване на още # приложение.}other{Показване на още # приложения.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Приложението се добавя на настолния компютър"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Отказ"</string>
</resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 7d8316c..0973a50 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"স্ক্রিনশট নিন"</string>
<string name="action_split" msgid="2098009717623550676">"স্প্লিট"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"স্প্লিট স্ক্রিন ব্যবহারের জন্য অ্যাপে ট্যাপ করুন"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"স্প্লিট স্ক্রিন বেছে নেওয়ার বিকল্প থেকে বেরিয়ে আসুন"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"স্প্লিট স্ক্রিন ব্যবহার করতে অন্য অ্যাপ বেছে নিন"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"এই অ্যাপ বা আপনার প্রতিষ্ঠান এই অ্যাকশনটি পারফর্ম করার অনুমতি দেয়নি"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"নেভিগেশন টিউটোরিয়াল এড়িয়ে যেতে চান?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"নিচে/ডানদিকে সরান"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{আরও #টি অ্যাপ দেখুন।}one{আরও #টি অ্যাপ দেখুন।}other{আরও #টি অ্যাপ দেখুন।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ও <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ডেস্কটপে অ্যাপ যোগ করা হচ্ছে"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"বাতিল করুন"</string>
</resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 56f2fa8..a2e4e33 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Snimak ekrana"</string>
<string name="action_split" msgid="2098009717623550676">"Podijeli"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Dodirnite drugu apl. da koristite podijeljeni ekran"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Izlaz iz odabira podijeljenog ekrana"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Odaberite drugu apl. da koristite podijeljeni ekran"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Ovu radnju ne dozvoljava aplikacija ili vaša organizacija"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Preskočiti vodič za navigiranje?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju.}one{Prikaži još # aplikaciju.}few{Prikaži još # aplikacije.}other{Prikaži još # aplikacija.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodavanje aplikacije na radnu površinu"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Otkaži"</string>
</resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index 6dff768..0cfe820 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Captura de pantalla"</string>
<string name="action_split" msgid="2098009717623550676">"Divideix"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toca una altra app per utilitzar pantalla dividida"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Surt de la selecció de pantalla dividida"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Tria una altra app per utilitzar pantalla dividida"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"L\'aplicació o la teva organització no permeten aquesta acció"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Vols ometre el tutorial de navegació?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mou a la part inferior o a la dreta"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostra # aplicació més.}other{Mostra # aplicacions més.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"S\'està afegint l\'aplicació a l\'ordinador"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel·la"</string>
</resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 40f8c3e..3582005 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Snímek obrazovky"</string>
<string name="action_split" msgid="2098009717623550676">"Rozdělit"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Obrazovku rozdělíte klepnutím na jinou aplikaci"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Výběr opuštění rozdělené obrazovky"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Vyberte podporovanou aplikaci"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Aplikace nebo organizace zakazuje tuto akci"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Přeskočit výukový program k navigaci?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Přesunout doprava dolů"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Zobrazit # další aplikaci.}few{Zobrazit # další aplikace.}many{Zobrazit # další aplikace.}other{Zobrazit # dalších aplikací.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> a <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Přidání aplikace na plochu"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Zrušit"</string>
</resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 7c786f3..1603fac 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Opdel"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tryk på en anden app for at bruge opdelt skærm"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Luk valg af opdelt skærm"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Vælg en anden app for at bruge opdelt skærm"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Appen eller din organisation tillader ikke denne handling"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Vil du springe vejledningen for navigation over?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flyt til bunden eller højre side"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Vis # app mere.}one{Vis # app mere.}other{Vis # apps mere.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Appen føjes til computeren"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuller"</string>
</resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 5fd1232..828b85f 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Teilen"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Für Splitscreen auf weitere App tippen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Splitscreen-Auswahl beenden"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Für Splitscreen andere App auswählen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Die App oder deine Organisation lässt diese Aktion nicht zu"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Tutorial zur Bedienung überspringen?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Nach unten / Nach rechts verschieben"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# weitere App anzeigen.}other{# weitere Apps anzeigen.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> und <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Hinzufügen einer App zum Desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Abbrechen"</string>
</resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index d1c16ba..ab9fe6d 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Στιγμιότυπο οθόνης"</string>
<string name="action_split" msgid="2098009717623550676">"Διαχωρισμός"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Πατήστε άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Έξοδος από την επιλογή διαχωρισμού οθόνης"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Επιλέξτε άλλη εφαρμογή για διαχωρισμό οθόνης"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Αυτή η ενέργεια δεν επιτρέπεται από την εφαρμογή ή τον οργανισμό σας."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Παράβλεψη οδηγού πλοήγησης;"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Μετακίνηση κάτω/δεξιά"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Εμφάνιση # ακόμα εφαρμογής.}other{Εμφάνιση # ακόμα εφαρμογών.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> και <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Γίνεται προσθήκη εφαρμογής στον υπολογιστή"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Ακύρωση"</string>
</resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index c2b000e..878c062 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Exit split screen selection"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choose another app to use split screen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"This action isn\'t allowed by the app or your organisation"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Skip navigation tutorial?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
</resources>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index 0def6cf..eed32b3 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Exit split screen selection"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choose another app to use split screen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"This action isn\'t allowed by the app or your organization"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Skip navigation tutorial?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to Desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
</resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index c2b000e..878c062 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Exit split screen selection"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choose another app to use split screen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"This action isn\'t allowed by the app or your organisation"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Skip navigation tutorial?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
</resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index c2b000e..878c062 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Exit split screen selection"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choose another app to use split screen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"This action isn\'t allowed by the app or your organisation"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Skip navigation tutorial?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
</resources>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
index 54b746b..ae825c1 100644
--- a/quickstep/res/values-en-rXC/strings.xml
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tap another app to use split screen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Exit split screen selection"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choose another app to use split screen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"This action isn\'t allowed by the app or your organization"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Skip navigation tutorial?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adding app to Desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancel"</string>
</resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 5dc9271..cf5d6e9 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Captura de pantalla"</string>
<string name="action_split" msgid="2098009717623550676">"Pantalla dividida"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Presiona otra app para usar la pantalla dividida"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Salir de la selección de pantalla dividida"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Elige otra app para usar la pantalla dividida"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"La app o tu organización no permiten realizar esta acción"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"¿Omitir el instructivo de navegación?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover a la parte inferior o derecha"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # app más.}other{Mostrar # apps más.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> y <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Agregando app al escritorio"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
</resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index ca8cbee..d40780c 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Hacer captura"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toca otra aplicación para usar la pantalla dividida"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Salir de la selección de pantalla dividida"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Elige otra app para usar la pantalla dividida"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"No puedes hacerlo porque la aplicación o tu organización no lo permiten"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"¿Saltar tutorial de navegación?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover abajo/a la derecha"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # aplicación más.}other{Mostrar # aplicaciones más.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> y <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Añadiendo aplicación al ordenador"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
</resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 0297d73..176741a 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Ekraanipilt"</string>
<string name="action_split" msgid="2098009717623550676">"Eralda"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Jagatud ekraanikuva kasutamiseks puudutage muud rakendust"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Jagatud ekraanikuva valikust väljumine"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Valige jagatud ekraanikuva jaoks muu rakendus"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Rakendus või teie organisatsioon on selle toimingu keelanud"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Kas jätta navigeerimise õpetused vahele?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Teisalda alla/paremale"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Kuva veel # rakendus.}other{Kuva veel # rakendust.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ja <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Rakenduse lisamine arvutisse"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Tühista"</string>
</resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 7d18d32..0d6c664 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Atera pantaila-argazki bat"</string>
<string name="action_split" msgid="2098009717623550676">"Zatitu"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Sakatu beste aplikazio bat pantaila zatitzeko"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Irten Pantaila zatitzea eginbidearen hautapenetik"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Pantaila zatitua ikusteko, aukeratu beste aplikazio bat"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Aplikazioak edo erakundeak ez du eman ekintza hori gauzatzeko baimena"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Nabigazio-tutoriala saltatu nahi duzu?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Eraman behera, eskuinetara"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Erakutsi beste # aplikazio.}other{Erakutsi beste # aplikazio.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> eta <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Aplikazioa mahaigainean gehitzen"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Utzi"</string>
</resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index b5545ef..85b4862 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"نماگرفت"</string>
<string name="action_split" msgid="2098009717623550676">"دونیمه"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"زدن روی برنامهای دیگر برای استفاده از صفحه دونیمه"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"خروج از انتخاب صفحهٔ دونیمه"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"انتخاب برنامهای دیگر برای استفاده از صفحه دونیمه"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"برنامه یا سازمان شما اجازه نمیدهد این کنش انجام شود."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"آموزش گامبهگام پیمایش رد شود؟"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"انتقال به پایین/ راست"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{نمایش # برنامه دیگر.}one{نمایش # برنامه دیگر.}other{نمایش # برنامه دیگر.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> و <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"درحال افزودن برنامه به رایانه"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"لغو"</string>
</resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 748a245..60d6618 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Kuvakaappaus"</string>
<string name="action_split" msgid="2098009717623550676">"Jaa"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Avaa jaettu näyttö napauttamalla toista sovellusta"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Poistu jaetun näytön valinnasta"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Käytä jaettua näyttöä valitsemalla toinen sovellus"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Sovellus tai organisaatio ei salli tätä toimintoa"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ohitetaanko navigointiohje?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Siirrä alas tai oikealle"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Näytä # muu sovellus.}other{Näytä # muuta sovellusta.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ja <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Sovelluksen lisääminen työpöydälle"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Peru"</string>
</resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index 4029bb2..a98e9c5 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Capture d\'écran"</string>
<string name="action_split" msgid="2098009717623550676">"Partager"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toucher une autre appli pour partager l\'écran"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Quitter la sélection d\'écran divisé"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choisir une autre application pour utiliser l\'écran partagé"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"L\'application ou votre organisation n\'autorise pas cette action"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ignorer le tutoriel sur la navigation?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer vers le coin inférieur droit de l\'écran"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre application.}one{Afficher # autre application.}other{Afficher # autres applications.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Ajout de l\'application au bureau en cours…"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuler"</string>
</resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index b86c18f..2e2e0f1 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Capture d\'écran"</string>
<string name="action_split" msgid="2098009717623550676">"Partager"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Appuyez sur autre appli pour l\'écran partagé"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Quitter la sélection de l\'écran partagé"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Sélect. autre appli pour utiliser l\'écran partagé"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Cette action n\'est pas autorisée par l\'application ou par votre organisation"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ignorer le tutoriel de navigation ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer en bas ou à droite"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli.}one{Afficher # autre appli.}other{Afficher # autre applis.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Ajout de l\'appli au bureau"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuler"</string>
</resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 53bc4d9..11479e1 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Facer captura"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Para usar a pantalla dividida, toca outra app"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Saír da selección de pantalla dividida"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Escolle outra app para usar a pantalla dividida"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"A aplicación ou a túa organización non permite realizar esta acción"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Queres omitir o titorial de navegación?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover á parte inferior ou á dereita"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # aplicación máis.}other{Mostrar # aplicacións máis.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Engadindo aplicación ao ordenador"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
</resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 519affd..ef44fb9 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"સ્ક્રીનશૉટ"</string>
<string name="action_split" msgid="2098009717623550676">"વિભાજિત કરો"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"વિભાજિત સ્ક્રીન વાપરવા, કોઈ અન્ય ઍપ પર ટૅપ કરો"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"\'સ્ક્રીનને વિભાજિત કરો\' પસંદગીમાંથી બહાર નીકળો"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"સ્ક્રીન વિભાજનનો ઉપયોગ કરવા કોઈ અન્ય ઍપ પસંદ કરો"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ઍપ કે તમારી સંસ્થા દ્વારા આ ક્રિયા કરવાની મંજૂરી નથી"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"નૅવિગેશન ટ્યૂટૉરિઅલ છોડી દઈએ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"સૌથી નીચે જમણી બાજુએ ખસેડો"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{વધુ # ઍપ બતાવો.}one{વધુ # ઍપ બતાવો.}other{વધુ # ઍપ બતાવો.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> અને <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ડેસ્કટૉપ પર ઍપ ઉમેરી રહ્યાં છીએ"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"રદ કરો"</string>
</resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 15ddc68..28551ad 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रीनशॉट लें"</string>
<string name="action_split" msgid="2098009717623550676">"स्प्लिट स्क्रीन मोड"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"स्प्लिट स्क्रीन के लिए दूसरे ऐप्लिकेशन पर टैप करें"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"स्प्लिट स्क्रीन मोड से बाहर निकलें"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"स्प्लिट स्क्रीन के लिए, दूसरा ऐप्लिकेशन चुनें"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ऐप्लिकेशन या आपका संगठन इस कार्रवाई की अनुमति नहीं देता"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"क्या आपको अभी नेविगेशन ट्यूटोरियल नहीं देखना है?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"नीचे/दाईं तरफ़ ले जाएं"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# और ऐप्लिकेशन दिखाएं.}one{# और ऐप्लिकेशन दिखाएं.}other{# और ऐप्लिकेशन दिखाएं.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> और <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"डेस्कटॉप पर ऐप्लिकेशन जोड़ा जा रहा है"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"रद्द करें"</string>
</resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 19786cd..abad768 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Snimka zaslona"</string>
<string name="action_split" msgid="2098009717623550676">"Podijeli"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Dodirnite drugu aplikaciju za podijeljeni zaslon"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Zatvori odabir podijeljenog zaslona"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Odaberite drugu aplikaciju za upotrebu podijeljenog zaslona"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Aplikacija ili vaša organizacija ne dopuštaju ovu radnju"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Želite li preskočiti vodič za kretanje?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje/desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži više aplikacija (još #).}one{Prikaži više aplikacija (još #).}few{Prikaži više aplikacija (još #).}other{Prikaži više aplikacija (još #).}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodavanje aplikacije na radnu površinu"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Odustani"</string>
</resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 6e4f860..16cd5f5 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Képernyőkép"</string>
<string name="action_split" msgid="2098009717623550676">"Felosztás"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Koppintson másik appra az osztott képernyőhöz"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Kilépés az osztott képernyő elemeinek kiválasztásából"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Válasszon másik appot a képernyő felosztásához"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Az alkalmazás vagy az Ön szervezete nem engedélyezi ezt a műveletet"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Kihagyja a navigáció bemutatóját?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mozgatás alulra vagy a jobb oldalra"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# további alkalmazás megjelenítése.}other{# további alkalmazás megjelenítése.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> és <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Alkalmazás hozzáadása az asztalhoz"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Mégse"</string>
</resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index b6bfa44..48eecd6 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Սքրինշոթ անել"</string>
<string name="action_split" msgid="2098009717623550676">"Տրոհել"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Հպեք այլ հավելվածի՝ տրոհված էկրանից օգտվելու համար"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Դուրս գալ տրոհված էկրանի ռեժիմից"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Ընտրեք այլ հավելված՝ կիսված էկրանից օգտվելու համար"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Այս գործողությունն արգելված է հավելվածի կամ ձեր կազմակերպության կողմից"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Բաց թողնե՞լ նավիգացիայի ուղեցույցը"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Տեղափոխել ներքևի աջ անկյուն"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Ցուցադրել ևս # հավելված։}one{Ցուցադրել ևս # հավելված։}other{Ցուցադրել ևս # հավելված։}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> և <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Հավելվածն ավելացվում է աշխատասեղանին"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Չեղարկել"</string>
</resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 27833a8..2852fc0 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Pisahkan"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ketuk aplikasi lain untuk memakai layar terpisah"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Keluar dari pemilihan layar terpisah"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Pilih aplikasi lain untuk memakai layar terpisah"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Tindakan ini tidak diizinkan oleh aplikasi atau organisasi Anda"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Lewati tutorial gestur?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pindahkan ke bawah/kanan"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Tampilkan # aplikasi lain.}other{Tampilkan # aplikasi lain.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dan <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Menambahkan aplikasi ke Desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Batalkan"</string>
</resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 3ff7185..998c23f 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Skjámynd"</string>
<string name="action_split" msgid="2098009717623550676">"Skipta"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ýttu á annað forrit til að nota skjáskiptingu"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Loka skjáskiptingu"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Veldu annað forrit til að nota skjáskiptingu"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Forritið eða fyrirtækið leyfir ekki þessa aðgerð"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Sleppa flettileiðsögn?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Færa neðst/til hægri"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Sýna # forrit í viðbót.}one{Sýna # forrit í viðbót.}other{Sýna # forrit í viðbót.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Forriti bætt við skjáborð"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Hætta við"</string>
</resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 5777911..3755dbb 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Dividi"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tocca un\'altra app per usare lo schermo diviso"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Esci dalla selezione dello schermo diviso"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Scegli un\'altra app per usare lo schermo diviso"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Questa azione non è consentita dall\'app o dall\'organizzazione"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Saltare il tutorial di navigazione?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sposta in basso/a destra"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostra # altra app.}other{Mostra altre # app.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Aggiunta app a desktop in corso…"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annulla"</string>
</resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 5c960c9..e07d338 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"צילום מסך"</string>
<string name="action_split" msgid="2098009717623550676">"פיצול"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"צריך להקיש על אפליקציה אחרת כדי להשתמש במסך מפוצל"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"יציאה מתצוגת מסך מפוצל"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"כדי להשתמש במסך מפוצל צריך לבחור אפליקציה אחרת"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"האפליקציה או הארגון שלך אינם מתירים את הפעולה הזאת"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"לדלג על המדריך לניווט?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"העברה לפינה הימנית/התחתונה"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{הצגת אפליקציה אחת (#) נוספת.}one{הצגת # אפליקציות נוספות.}two{הצגת # אפליקציות נוספות.}other{הצגת # אפליקציות נוספות.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ו-<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"האפליקציה מתווספת לשולחן העבודה"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ביטול"</string>
</resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 13a3425..7517f9d 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"スクリーンショット"</string>
<string name="action_split" msgid="2098009717623550676">"分割"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"分割画面を使用するには、他のアプリをタップします"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"分割画面の選択を終了します"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"分割画面にするには、別のアプリを選択してください"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"この操作はアプリまたは組織で許可されていません"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"操作チュートリアルをスキップしますか?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"下 / 右に移動"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{他 # 件のアプリを表示できます。}other{他 # 件のアプリを表示できます。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> と <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"アプリをデスクトップに追加する"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"キャンセル"</string>
</resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index d987ac0..64aa870 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ეკრანის ანაბეჭდი"</string>
<string name="action_split" msgid="2098009717623550676">"გაყოფა"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"შეეხეთ სხვა აპს ეკრანის გასაყოფად"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ეკრანის გაყოფის არჩევანიდან გასვლა"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"აირჩიეთ სხვა აპი ეკრანის გასაყოფად"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ეს მოქმედება არ არის დაშვებული აპის ან თქვენი ორგანიზაციის მიერ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"გსურთ, გამოტოვოთ ნავიგაციის სახელმძღვანელო?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ქვემოთ/მარჯვნივ გადატანა"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{#-ით მეტი აპის ჩენება}other{#-ით მეტი აპის ჩვენება.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> და <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"მიმდინარეობს აპის დესკტოპზე დამატება"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"გაუქმება"</string>
</resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 8a9f94e..cde0dfd 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
<string name="action_split" msgid="2098009717623550676">"Бөлу"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Экранды бөлу режимін пайдалану үшін басқа қолданбаны түртіңіз."</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Экранды бөлу режимінен шығу"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Экранды бөлу үшін басқа қолданбаны таңдаңыз."</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Бұл әрекетке қолданба не ұйым рұқсат етпейді."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Қимылдар оқулығын өткізіп жіберу керек пе?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмен/оңға жылжыту"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Тағы # қолданбаны көрсету.}other{Тағы # қолданбаны көрсету.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> және <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Жұмыс үстеліне қолданба қосу"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Бас тарту"</string>
</resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 0b12d79..89d8335 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"រូបថតអេក្រង់"</string>
<string name="action_split" msgid="2098009717623550676">"បំបែក"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ចុចកម្មវិធីផ្សេងទៀត ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ចាកចេញពីការជ្រើសរើសរបស់មុខងារបំបែកអេក្រង់"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"ជ្រើសរើសកម្មវិធីផ្សេងទៀត ដើម្បីប្រើមុខងារបំបែកអេក្រង់"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"សកម្មភាពនេះមិនត្រូវបានអនុញ្ញាតដោយកម្មវិធី ឬស្ថាប័នរបស់អ្នកទេ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"រំលងមេរៀនអំពីការរុករកឬ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ផ្លាស់ទីទៅខាងក្រោម/ស្ដាំ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{បង្ហាញកម្មវិធី # ទៀត។}other{បង្ហាញកម្មវិធី # ទៀត។}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> និង <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"កំពុងបញ្ចូលកម្មវិធីទៅកុំព្យូទ័រ"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"បោះបង់"</string>
</resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 2666846..b7cdd15 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ಸ್ಕ್ರೀನ್ಶಾಟ್"</string>
<string name="action_split" msgid="2098009717623550676">"ವಿಭಜಿಸಿ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಬಳಸಲು ಬೇರೆ ಆ್ಯಪ್ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಆಯ್ಕೆಯಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"\"ಪರದೆ ಬೇರ್ಪಡಿಸಿ\" ಬಳಸಲು ಬೇರೆ ಆ್ಯಪ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ಆ್ಯಪ್ ಅಥವಾ ನಿಮ್ಮ ಸಂಸ್ಥೆಯು ಈ ಕ್ರಿಯೆಯನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ನ್ಯಾವಿಗೇಶನ್ ಟ್ಯುಟೋರಿಯಲ್ ಸ್ಕಿಪ್ ಮಾಡಬೇಕೇ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ಕೆಳಗಿನ/ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ಇನ್ನೂ # ಆ್ಯಪ್ ಅನ್ನು ತೋರಿಸಿ.}one{ಇನ್ನೂ # ಆ್ಯಪ್ಗಳನ್ನು ತೋರಿಸಿ.}other{ಇನ್ನೂ # ಆ್ಯಪ್ಗಳನ್ನು ತೋರಿಸಿ.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ಮತ್ತು <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ಡೆಸ್ಕ್ಟಾಪ್ಗೆ ಆ್ಯಪ್ ಅನ್ನು ಸೇರಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ರದ್ದುಮಾಡಿ"</string>
</resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 8a4c9b4..c00eeed 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"스크린샷"</string>
<string name="action_split" msgid="2098009717623550676">"분할"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"다른 앱을 탭하여 화면 분할 사용"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"화면 분할 선택 종료"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"화면 분할을 사용하려면 다른 앱을 선택하세요."</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"이 작업은 앱 또는 조직에서 허용되지 않습니다."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"이동 방법 튜토리얼을 건너뛰시겠습니까?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"하단/오른쪽으로 이동"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{앱 #개 더 표시}other{앱 #개 더 표시}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> 및 <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"데스크톱에 앱 추가하기"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"취소"</string>
</resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 7c74fa6..f5d8782 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
<string name="action_split" msgid="2098009717623550676">"Бөлүү"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Экранды бөлүү үчүн башка колдонмону таптап коюңуз"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Тандалган экранды бөлүүдөн чыгуу"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Экранды бөлүү үчүн башка колдонмону тандаңыз"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Бул аракетти аткарууга колдонмо же ишканаңыз тыюу салган"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Жаңсоолор үйрөткүчүн өткөрүп жибересизби?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмөнкү/оң бурчка жылдыруу"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Дагы # колдонмону көрсөтүү.}other{Дагы # колдонмону көрсөтүү.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> жана <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Колдонмону иш тактага кошуу"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Жокко чыгаруу"</string>
</resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 66db658..2f67c30 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ຮູບໜ້າຈໍ"</string>
<string name="action_split" msgid="2098009717623550676">"ແບ່ງ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ແຕະແອັບອື່ນເພື່ອໃຊ້ໜ້າຈໍແຍກ"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ອອກຈາກາກນເລືອກການແບ່ງໜ້າຈໍ"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"ເລືອກແອັບອື່ນເພື່ອໃຊ້ການແບ່ງໜ້າຈໍ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ແອັບ ຫຼື ອົງການຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ໃຊ້ຄຳສັ່ງນີ້"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ຂ້າມການສອນການນຳໃຊ້ການນຳທາງບໍ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ຍ້າຍໄປຂວາ/ລຸ່ມ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ສະແດງອີກ # ແອັບ.}other{ສະແດງອີກ # ແອັບ.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ແລະ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ການເພີ່ມແອັບໄປໃສ່ເດັສທັອບ"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ຍົກເລີກ"</string>
</resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 91909e9..8bb1252 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Ekrano kopija"</string>
<string name="action_split" msgid="2098009717623550676">"Išskaidymo režimas"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Išskaidyto ekrano režimas palietus kitą programą"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Išeiti iš išskaidyto ekrano pasirinkimo"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Išskaidyto ekrano režimą naudokite kita programa"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Jūsų organizacijoje arba naudojant šią programą neleidžiama atlikti šio veiksmo"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Praleisti naršymo mokymo programą?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Perkelti žemyn, dešinėn"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Rodyti dar # programą.}one{Rodyti dar # programą.}few{Rodyti dar # programas.}many{Rodyti dar # programos.}other{Rodyti dar # programų.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"„<xliff:g id="APP_NAME_1">%1$s</xliff:g>“ ir „<xliff:g id="APP_NAME_2">%2$s</xliff:g>“"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Pridedama programa prie darbalaukio"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Atšaukti"</string>
</resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index c3cd847..a8bff55 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Veikt ekrānuzņēmumu"</string>
<string name="action_split" msgid="2098009717623550676">"Sadalīt"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Lai sadalītu ekrānu, pieskarieties citai lietotnei"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Izejiet no ekrāna sadalīšanas režīma atlases."</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Izvēlieties citu lietotni, lai sadalītu ekrānu"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Lietotne vai jūsu organizācija neatļauj veikt šo darbību."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Vai izlaist navigācijas mācības?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pārvietot uz apakšējo/labo stūri"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Rādīt vēl # lietotni}zero{Rādīt vēl # lietotnes}one{Rādīt vēl # lietotni}other{Rādīt vēl # lietotnes}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"“<xliff:g id="APP_NAME_1">%1$s</xliff:g>” un “<xliff:g id="APP_NAME_2">%2$s</xliff:g>”"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Notiek lietotnes pievienošana datoram"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Atcelt"</string>
</resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 9122310..258a544 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Слика од екранот"</string>
<string name="action_split" msgid="2098009717623550676">"Раздели"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Допрете друга аплик. за да користите поделен екран"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Излези од изборот на поделен екран"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Изберете друга апликација за да користите поделен екран"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Апликацијата или вашата организација не го дозволува дејствово"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Да се прескокне упатството за навигација?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести долу десно"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Прикажи уште # апликација.}one{Прикажи уште # апликација.}other{Прикажи уште # апликации.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Додавање на апликацијата во „Работна површина“"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Откажи"</string>
</resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index b6ad6ac..fffcd54 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"സ്ക്രീൻഷോട്ട്"</string>
<string name="action_split" msgid="2098009717623550676">"വിഭജിക്കുക"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"സ്പ്ലിറ്റ് സ്ക്രീനിന് മറ്റൊരു ആപ്പിൽ ടാപ്പ് ചെയ്യൂ"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"സ്ക്രീൻ വിഭജന തിരഞ്ഞെടുപ്പിൽ നിന്ന് പുറത്തുകടക്കുക"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"സ്ക്രീൻ വിഭജന മോഡിന് മറ്റൊരു ആപ്പ് തിരഞ്ഞെടുക്കൂ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ഈ നടപടി എടുക്കുന്നത് ആപ്പോ നിങ്ങളുടെ സ്ഥാപനമോ അനുവദിക്കുന്നില്ല"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"നാവിഗേഷൻ ട്യൂട്ടോറിയൽ ഒഴിവാക്കണോ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"താഴേക്കോ വലത്തേക്കോ നീക്കുക"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# ആപ്പ് കൂടി കാണിക്കുക.}other{# ആപ്പുകൾ കൂടി കാണിക്കുക.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>, <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ആപ്പ് ഡെസ്ക്ടോപ്പിലേക്ക് ചേർക്കുന്നു"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"റദ്ദാക്കുക"</string>
</resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 8f82ebd..8c7ae78 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Дэлгэцийн агшин дарах"</string>
<string name="action_split" msgid="2098009717623550676">"Хуваах"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Дэлгэцийг хуваахыг ашиглахын тулд өөр аппыг товш"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Дэлгэцийг хуваах сонголтоос гарах"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Дэлгэцийг хуваах горим ашиглах өөр апп сонгоно уу"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Энэ үйлдлийг апп эсвэл танай байгууллага зөвшөөрдөггүй"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Навигацын практик хичээлийг алгасах уу?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Баруун доод хэсэг рүү зөөх"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Өөр # аппыг харуулна уу.}other{Өөр # аппыг харуулна уу.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> болон <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Компьютерт апп нэмж байна"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Цуцлах"</string>
</resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 73260b2..5bf47ce 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रीनशॉट"</string>
<string name="action_split" msgid="2098009717623550676">"स्प्लिट"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"स्प्लिट स्क्रीन वापरण्यासाठी दुसऱ्या ॲपवर टॅप करा"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"स्प्लिट स्क्रीन निवडीतून बाहेर पडा"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप निवडा"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"अॅप किंवा तुमच्या संस्थेद्वारे ही क्रिया करण्याची अनुमती नाही"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"नेव्हिगेशन ट्यूटोरियल वगळायचे आहे का?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"तळाशी/उजवीकडे हलवा"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{आणखी # अॅप दाखवा.}other{आणखी # अॅप्स दाखवा.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> आणि <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"डेस्कटॉपवर ॲप जोडत आहे"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"रद्द करा"</string>
</resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index f9b0225..5e61b88 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Tangkapan skrin"</string>
<string name="action_split" msgid="2098009717623550676">"Pisah"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ketik apl lain untuk menggunakan skrin pisah"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Keluar daripada pilihan skrin pisah"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Pilih apl lain untuk menggunakan skrin pisah"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Tindakan ini tidak dibenarkan oleh apl atau organisasi anda"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Langkau tutorial navigasi?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Alihkan ke bawah/kanan"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Tunjukkan # lagi apl.}other{Tunjukkan # lagi apl.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dan <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Menambahkan apl pada Desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Batal"</string>
</resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 14595cf..6f1a276 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
<string name="action_split" msgid="2098009717623550676">"ခွဲထုတ်ရန်"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"မျက်နှာပြင် ခွဲ၍ပြသရန် အက်ပ်နောက်တစ်ခုကို တို့ပါ"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ရွေးချယ်မှုမှ ထွက်ရန်"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"မျက်နှာပြင်ခွဲ၍ပြသခြင်းသုံးရန် နောက်အက်ပ်တစ်ခုရွေးပါ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ဤလုပ်ဆောင်ချက်ကို အက်ပ် သို့မဟုတ် သင်၏အဖွဲ့အစည်းက ခွင့်မပြုပါ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"လမ်းညွှန်ခြင်း ရှင်းလင်းပို့ချချက်ကို ကျော်မလား။"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"အောက်ခြေ/ညာဘက်သို့ ရွှေ့ရန်"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{နောက်ထပ်အက်ပ် # ခု ပြပါ။}other{နောက်ထပ်အက်ပ် # ခု ပြပါ။}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> နှင့် <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"‘ဒက်စ်တော့’ တွင် အက်ပ်ကို ထည့်ခြင်း"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"မလုပ်တော့"</string>
</resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 5fe4abd..d4b8de9 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Skjermdump"</string>
<string name="action_split" msgid="2098009717623550676">"Del opp"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Trykk på en annen app for å bruke delt skjerm"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Avslutt valg av delt skjerm"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Velg en annen app for å bruke delt skjerm"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Appen eller organisasjonen din tillater ikke denne handlingen"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Vil du hoppe over navigeringsveiledningen?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytt til nederst/høyre"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Vis # app til.}other{Vis # apper til.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Legg til apper på datamaskin"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Avbryt"</string>
</resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 7a86f28..b326cb4 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रिनसट"</string>
<string name="action_split" msgid="2098009717623550676">"स्प्लिट गर्नुहोस्"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"स्प्लिटस्क्रिन प्रयोग गर्न अर्को एपमा ट्याप गर्नु…"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"स्प्लिट स्क्रिन मोडबाट बाहिरिनुहोस्"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"स्प्लिट स्क्रिन प्रयोग गर्न अर्को एप रोज्नुहोस्"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"यो एप वा तपाईंको सङ्गठनले यो कारबाही गर्ने अनुमति दिँदैन"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"नेभिगेसन ट्युटोरियल स्किप गर्ने हो?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"फेद/दायाँतिर सार्नुहोस्"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{थप # एप देखाइयोस्।}other{थप # वटा एप देखाइयोस्।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> र <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"डेस्कटपमा एप हालिँदै छ"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"रद्द गर्नुहोस्"</string>
</resources>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index ed63bad..6a7efe5 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Splitsen"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tik op nog een app om je scherm te splitsen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Sluit de selectie voor gesplitst scherm"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Kies andere app om gesplitst scherm te gebruiken"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Deze actie wordt niet toegestaan door de app of je organisatie"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Navigatietutorial overslaan?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Naar beneden/rechts verplaatsen"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Nog # app tonen.}other{Nog # apps tonen.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> en <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"App toevoegen aan desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuleren"</string>
</resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 5e2a16e..f0a5053 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ସ୍କ୍ରିନସଟ୍"</string>
<string name="action_split" msgid="2098009717623550676">"ସ୍ପ୍ଲିଟ୍"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ସ୍ପ୍ଲିଟସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପରେ ଟାପ କର"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଚୟନରୁ ବାହାରି ଯାଆନ୍ତୁ"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଅନ୍ୟ ଏକ ଆପ ବାଛନ୍ତୁ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ଆପ୍ କିମ୍ବା ଆପଣଙ୍କ ସଂସ୍ଥା ଦ୍ୱାରା ଏହି କାର୍ଯ୍ୟକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ନାଭିଗେସନ୍ ଟ୍ୟୁଟୋରିଆଲକୁ ବାଦ୍ ଦେବେ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ନିମ୍ନ/ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ଅଧିକ #ଟି ଆପ ଦେଖାନ୍ତୁ।}other{ଅଧିକ #ଟି ଆପ୍ସ ଦେଖାନ୍ତୁ।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ଏବଂ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ଡେସ୍କଟପରେ ଆପ ଯୋଗ କରାଯାଉଛି"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ବାତିଲ କରନ୍ତୁ"</string>
</resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 88bc4eb..c66e26f 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
<string name="action_split" msgid="2098009717623550676">"ਸਪਲਿਟ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨੂੰ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਚੋਣ ਤੋਂ ਬਾਹਰ ਜਾਓ"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਰਤਣ ਲਈ ਕਿਸੇ ਹੋਰ ਐਪ ਨੂੰ ਚੁਣੋ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ਐਪ ਜਾਂ ਤੁਹਾਡੀ ਸੰਸਥਾ ਵੱਲੋਂ ਇਸ ਕਾਰਵਾਈ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ਕੀ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਛੱਡਣਾ ਹੈ?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ਹੇਠਾਂ/ਸੱਜੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}one{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}other{# ਹੋਰ ਐਪਾਂ ਦਿਖਾਓ।}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ਅਤੇ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ਐਪ ਨੂੰ ਡੈਸਕਟਾਪ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ਰੱਦ ਕਰੋ"</string>
</resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 4077212..7f0600f 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Zrzut ekranu"</string>
<string name="action_split" msgid="2098009717623550676">"Podziel"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Aby podzielić ekran, kliknij drugą aplikację"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Wyjdź z wyboru podzielonego ekranu"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Wybierz drugą aplikację, aby podzielić ekran"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Nie możesz wykonać tego działania, bo nie zezwala na to aplikacja lub Twoja organizacja"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Pominąć samouczek nawigacji?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Przesuń w dolny prawy róg"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Pokaż jeszcze # aplikację.}few{Pokaż jeszcze # aplikacje.}many{Pokaż jeszcze # aplikacji.}other{Pokaż jeszcze # aplikacji.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodaję aplikację do komputera"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Anuluj"</string>
</resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index a886799..791a127 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Fazer captura de ecrã"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toque noutra app para usar o ecrã dividido"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Saia da seleção de ecrã dividido"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Escolher outra app para usar o ecrã dividido"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Esta ação não é permitida pela app ou a sua entidade."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ignorar o tutorial de navegação?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para a part superior direita"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar mais # app.}other{Mostrar mais # apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"A adicionar a app ao computador"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
</resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 1430885..c943da5 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Capturar tela"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toque em outro app para usar a tela dividida"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Sair da seleção de tela dividida"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Escolha outro app para usar na tela dividida"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Essa ação não é permitida pelo app ou pela organização"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Pular o tutorial de navegação?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para baixo/para a direita"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar mais # app.}one{Mostrar mais # app.}other{Mostrar mais # apps.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Adicionando app ao computador"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Cancelar"</string>
</resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 1948599..5db7859 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Captură de ecran"</string>
<string name="action_split" msgid="2098009717623550676">"Împărțit"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Atinge altă aplicație pentru ecranul împărțit"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Ieși din selecția cu ecran împărțit"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Alege altă aplicație pentru ecranul împărțit"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Această acțiune nu este permisă de aplicație sau de organizația ta"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Omiți tutorialul de navigare?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mută în dreapta jos"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afișează încă # aplicație}few{Afișează încă # aplicații}other{Afișează încă # de aplicații}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> și <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Se adaugă aplicația pe computer"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Anulează"</string>
</resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index c62fcca..a11175e 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
<string name="action_split" msgid="2098009717623550676">"Разделить"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Для разделения экрана выберите другое приложение."</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Выйдите из режима разделения экрана."</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Выберите другое приложение для разделения экрана."</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Это действие заблокировано приложением или организацией."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Пропустить руководство по жестам?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Переместить вниз или вправо"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показать ещё # приложение}one{Показать ещё # приложение}few{Показать ещё # приложения}many{Показать ещё # приложений}other{Показать ещё # приложения}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Добавление приложения на компьютер"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Отмена"</string>
</resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 2c1e67d..3722b2f 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"තිර රුව"</string>
<string name="action_split" msgid="2098009717623550676">"බෙදන්න"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"බෙදුම් තිරය භාවිතා කිරීමට තවත් යෙදුමක් තට්ටු කරන්න"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"බෙදීම් තිර තේරීමෙන් පිටවන්න"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"බෙදීම් තිරය භාවිතා කිරීමට වෙනත් යෙදුමක් තෝරා ගන්න"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"මෙම ක්රියාව යෙදුම හෝ ඔබේ සංවිධානය මගින් ඉඩ නොදේ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"නිබන්ධනය සංචාලනය මඟ හරින්නද?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"පහළ/දකුණ වෙත ගෙන යන්න"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{තවත් # යෙදුමක් පෙන්වන්න.}one{තවත් යෙදුම් #ක් පෙන්වන්න.}other{තවත් යෙදුම් #ක් පෙන්වන්න.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> සහ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ඩෙස්ක්ටොප් වෙත යෙදුම එක් කිරීම"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"අවලංගු කරන්න"</string>
</resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index b0e859b..aee281c 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Snímka obrazovky"</string>
<string name="action_split" msgid="2098009717623550676">"Rozdeliť"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Obrazovku rozdelíte klepnutím na inú aplikáciu"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Ukončite výber rozdelenej obrazovky"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Na použitie rozd. obrazovky vyberte inú aplikáciu"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Aplikácia alebo vaša organizácia túto akciu nepovoľuje"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Chcete preskočiť návod na navigáciu?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Presunúť dole alebo doprava"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Zobraziť # ďalšiu aplikáciu.}few{Zobraziť # ďalšie aplikácie.}many{Show # more apps.}other{Zobraziť # ďalších aplikácií.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> a <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Pridanie aplikácie na plochu"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Zrušiť"</string>
</resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index e72dac1..4787f04 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Posnetek zaslona"</string>
<string name="action_split" msgid="2098009717623550676">"Razdeli"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Za razdeljeni zaslon se dotaknite še 1 aplikacije"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Zapri izbiro razdeljenega zaslona"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Izberite drugo aplikacijo za uporabo razdeljenega zaslona."</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Aplikacija ali vaša organizacija ne dovoljuje tega dejanja"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Želite preskočiti vadnico za krmarjenje?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premakni na dno/desno"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Pokaži še # aplikacijo.}one{Pokaži še # aplikacijo.}two{Pokaži še # aplikaciji.}few{Pokaži še # aplikacije.}other{Pokaži še # aplikacij.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> in <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Dodajanje aplikacije na namizje"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Prekliči"</string>
</resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 6f661c3..a9e8b94 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Pamja e ekranit"</string>
<string name="action_split" msgid="2098009717623550676">"Ndaj"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Trokit një apl. tjetër; përdor ekranin e ndarë"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Dil nga zgjedhja e ekranit të ndarë"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Zgjidh një aplikacion tjetër për të përdorur ekranin e ndarë"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Ky veprim nuk lejohet nga aplikacioni ose organizata jote"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Të kapërcehet udhëzuesi i navigimit?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Lëviz në fund/djathtas"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Shfaq # aplikacion tjetër.}other{Shfaq # aplikacione të tjera.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dhe <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Shtimi i aplikacionit te desktopi"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Anulo"</string>
</resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index c985e08..da057d8 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Снимак екрана"</string>
<string name="action_split" msgid="2098009717623550676">"Подели"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Додирните другу апликацију за подељени екран"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Излазак из бирања подељеног екрана"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Одаберите другу апликацију за подељени екран"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Апликација или организација не дозвољавају ову радњу"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Желите да прескочите водич за кретање?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести доле десно"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Прикажи још # апликацију.}one{Прикажи још # апликацију.}few{Прикажи још # апликације.}other{Прикажи још # апликација.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Додаје се апликација на радну поврршину"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Откажи"</string>
</resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 7e97556..31853f9 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Skärmbild"</string>
<string name="action_split" msgid="2098009717623550676">"Delat"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Tryck på en annan app för att använda delad skärm"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Avsluta val av delad skärm"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Välj en annan app för att använda delad skärm"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Appen eller organisationen tillåter inte den här åtgärden"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Vill du hoppa över självstudierna?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytta längst ned/till höger"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Visa # app till.}other{Visa # appar till.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> och <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Lägger till appen på skrivbordet"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Avbryt"</string>
</resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 983964b..8c438f4 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Picha ya skrini"</string>
<string name="action_split" msgid="2098009717623550676">"Iliyogawanywa"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Gusa programu nyingine ili utumie kipengele cha kugawa skrini"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Ondoka kwenye hali ya skrini iliyogawanywa"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Chagua programu nyingine ili utumie hali ya kugawa skrini"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Kitendo hiki hakiruhusiwi na programu au shirika lako"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ungependa kuruka mafunzo ya usogezaji?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sogeza chini/kulia"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Onyesha programu # zaidi.}other{Onyesha programu # zaidi.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> na <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Kuweka programu kwenye Eneo-kazi"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Ghairi"</string>
</resources>
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
index 9e832bc..1caffb8 100644
--- a/quickstep/res/values-sw720dp/dimens.xml
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -43,4 +43,7 @@
<dimen name="taskbar_app_window_threshold">100dp</dimen>
<dimen name="taskbar_home_overview_threshold">180dp</dimen>
<dimen name="taskbar_catch_up_threshold">300dp</dimen>
+
+ <!-- Taskbar swipe up threshold multipliers -->
+ <item name="taskbar_nav_threshold_mult" format="float" type="dimen">3</item>
</resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 2016c9f..ef5bd3f 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ஸ்கிரீன்ஷாட்"</string>
<string name="action_split" msgid="2098009717623550676">"பிரி"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"திரைப் பிரிப்பைப் பயன்படுத்த வேறு ஆப்ஸைத் தட்டவும்"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"திரைப் பிரிப்பு தேர்வில் இருந்து வெளியேறும்"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"திரைப் பிரிப்பை பயன்படுத்த வேறு ஆப்ஸை தேர்வுசெய்க"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ஆப்ஸோ உங்கள் நிறுவனமோ இந்த செயலை அனுமதிப்பதில்லை"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"வழிகாட்டுதல் பயிற்சியைத் தவிர்க்கவா?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"கீழே/வலதுபுறம் நகர்த்தும்"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{மேலும் # ஆப்ஸைக் காட்டு.}other{மேலும் # ஆப்ஸைக் காட்டு.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> மற்றும் <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ஆப்ஸை டெஸ்க்டாப்பில் சேர்க்கிறது"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ரத்துசெய்"</string>
</resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index b9d4b31..a2adfe1 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"స్క్రీన్షాట్"</string>
<string name="action_split" msgid="2098009717623550676">"స్ప్లిట్ చేయండి"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"స్ప్లిట్ స్క్రీన్ కోసం మరొక యాప్ను ట్యాప్ చేయండి"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"స్ప్లిట్ స్క్రీన్ ఎంపిక నుండి ఎగ్జిట్ అవ్వండి"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"స్ప్లిట్ స్క్రీన్ ఉపయోగానికి మరొక యాప్ ఎంచుకోండి"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ఈ చర్యను యాప్ గానీ, మీ సంస్థ గానీ అనుమతించవు"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"నావిగేషన్ ట్యుటోరియల్ను స్కిప్ చేయాలా?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"దిగువ/కుడి వైపునకు తరలించండి"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{మరో # యాప్ను చూడండి.}other{మరో # యాప్లను చూడండి.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>, <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"డెస్క్టాప్నకు యాప్ను జోడిస్తోంది"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"రద్దు చేయండి"</string>
</resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 0f921d1..0339046 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"ภาพหน้าจอ"</string>
<string name="action_split" msgid="2098009717623550676">"แยก"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"แตะแอปอื่นเพื่อใช้การแยกหน้าจอ"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"ออกจากการเลือกโหมดแยกหน้าจอ"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"เลือกแอปอื่นเพื่อใช้การแยกหน้าจอ"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"แอปหรือองค์กรของคุณไม่อนุญาตการดำเนินการนี้"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ข้ามบทแนะนำการนำทางไหม"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ย้ายไปที่ด้านล่างหรือด้านขวา"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{แสดงเพิ่มเติมอีก # แอป}other{แสดงเพิ่มเติมอีก # แอป}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> และ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"การเพิ่มแอปไปยังเดสก์ท็อป"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"ยกเลิก"</string>
</resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 7185cb0..7bb38c2 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Split"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Mag-tap ng ibang app para gamitin ang split screen"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Lumabas sa pagpili ng split screen"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Pumili ng ibang app para gamitin ang split screen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Hindi pinapayagan ng app o ng iyong organisasyon ang pagkilos na ito"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Laktawan ang tutorial sa pag-navigate?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Ilipat sa ibaba/kanan"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Magpakita ng # pang app.}one{Magpakita ng # pang app.}other{Magpakita ng # pang app.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> at <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Idinaragdag ang app sa Desktop"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Kanselahin"</string>
</resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 1648b22..905183a 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Ekran görüntüsü"</string>
<string name="action_split" msgid="2098009717623550676">"Böl"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Bölünmüş ekran için başka bir uygulamaya dokunun"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Bölünmüş ekran seçiminden çıkın"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Bölünmüş ekran kullanmak için başka bir uygulama seçin"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Uygulamanız veya kuruluşunuz bu işleme izin vermiyor"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Gezinme eğitimi atlansın mı?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sağ alta taşı"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# uygulama daha göster.}other{# uygulama daha göster}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ve <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Uygulama Masaüstü\'ne ekleniyor"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"İptal"</string>
</resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 1f91165..6cbf71f 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Знімок екрана"</string>
<string name="action_split" msgid="2098009717623550676">"Розділити"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Щоб розділити екран, виберіть ще один додаток"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Вийти з режиму розділення екрана"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Щоб розділити екран, виберіть ще один додаток"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Ця дія заборонена додатком або адміністратором організації"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Пропустити посібник із навігації?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перемістити вниз або вправо"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показати ще # додаток.}one{Показати ще # додаток.}few{Показати ще # додатки.}many{Показати ще # додатків.}other{Показати ще # додатка.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> та <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Встановлення додатка на комп’ютер"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Скасувати"</string>
</resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 504f3aa..2392c6c 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"اسکرین شاٹ"</string>
<string name="action_split" msgid="2098009717623550676">"اسپلٹ"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"اسپلٹ اسکرین کا استعمال کرنے کیلئے دوسری ایپ پر تھپتھپائیں"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"اسپلٹ اسکرین کے انتخاب سے باہر نکلیں"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"اسپلٹ اسکرین کے استعمال کیلئے دوسری ایپ منتخب کریں"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"ایپ یا آپ کی تنظیم کی جانب سے اس کارروائی کی اجازت نہیں ہے"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"نیویگیشن کا ٹیوٹوریل نظر انداز کریں؟"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"نیچے/دائیں طرف منتقل کریں"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# مزید ایپ دکھائیں۔}other{# مزید ایپس دکھائیں۔}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> اور <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"ڈیسک ٹاپ پر ایپ شامل کرنا"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"منسوخ کریں"</string>
</resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 9f00663..da2d363 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Skrinshot"</string>
<string name="action_split" msgid="2098009717623550676">"Ajratish"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Ekranni ikkiga ajratish uchun boshqa ilovani bosing"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Ekranni ikkiga ajratish tanlovidan chiqish"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Ekranni ikkiga ajratish uchun boshqa ilovani tanlang"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Bu amal ilova yoki tashkilotingiz tomonidan taqiqlangan"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Navigatsiya darsi yopilsinmi?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pastga yoki oʻngga oʻtkazish"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Yana # ta ilovani chiqarish}other{Yana # ta ilovani chiqarish}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> va <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Ilova kompyuterga qoʻshilmoqda"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Bekor qilish"</string>
</resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 48bc1cb..dae4212 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Chụp ảnh màn hình"</string>
<string name="action_split" msgid="2098009717623550676">"Chia đôi màn hình"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Nhấn vào ứng dụng khác để chia đôi màn hình"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Thoát khỏi lựa chọn chia đôi màn hình"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Chọn một ứng dụng khác để dùng chế độ chia đôi màn hình"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Ứng dụng hoặc tổ chức của bạn không cho phép thực hiện hành động này"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Bỏ qua phần hướng dẫn thao tác?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Chuyển xuống dưới cùng/sang bên phải"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Hiện thêm # ứng dụng.}other{Hiện thêm # ứng dụng.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> và <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Đang thêm ứng dụng vào máy tính"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Huỷ"</string>
</resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 80d7525..3980b21 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"屏幕截图"</string>
<string name="action_split" msgid="2098009717623550676">"拆分"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"点按另一个应用即可使用分屏"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"退出分屏选择模式"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"另外选择一个应用才可使用分屏模式"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"该应用或您所在的单位不允许执行此操作"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"要跳过导航教程吗?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右侧"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{显示另外 # 个应用。}other{显示另外 # 个应用。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>和<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"将应用添加到桌面"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"取消"</string>
</resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 5eaf95e..0532ae5 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"螢幕截圖"</string>
<string name="action_split" msgid="2098009717623550676">"分割"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"輕按其他應用程式以使用分割螢幕"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"退出分割螢幕選取頁面"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"選擇其他應用程式才能使用分割螢幕"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"應用程式或你的機構不允許此操作"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"要略過手勢操作教學課程嗎?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移至底部/右側"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{顯示另外 # 個應用程式。}other{顯示另外 # 個應用程式。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"「<xliff:g id="APP_NAME_1">%1$s</xliff:g>」和「<xliff:g id="APP_NAME_2">%2$s</xliff:g>」"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"正在新增應用程式至桌面"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"取消"</string>
</resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index b7e6e06..214fcfd 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"螢幕截圖"</string>
<string name="action_split" msgid="2098009717623550676">"分割"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"輕觸另一個應用程式即可使用分割畫面"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"退出分割畫面選擇器"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"必須選擇另一個應用程式才能使用分割畫面"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"這個應用程式或貴機構不允許執行這個動作"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"要略過手勢操作教學課程嗎?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右側"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{顯示另外 # 個應用程式。}other{顯示另外 # 個應用程式。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"「<xliff:g id="APP_NAME_1">%1$s</xliff:g>」和「<xliff:g id="APP_NAME_2">%2$s</xliff:g>」"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"新增應用程式至桌面"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"取消"</string>
</resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 9ca2106..9655a44 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -95,6 +95,7 @@
<string name="action_screenshot" msgid="8171125848358142917">"Isithombe-skrini"</string>
<string name="action_split" msgid="2098009717623550676">"Hlukanisa"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Thepha enye i-app ukuze usebenzise isikrini sokuhlukanisa"</string>
+ <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Phuma ekukhetheni ukuhlukaniswa kwesikrini"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Khetha enye i-app ukuze usebenzise ukuhlukanisa isikrini"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Lesi senzo asivunyelwanga uhlelo lokusebenza noma inhlangano yakho"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Yeqa isifundo sokuzulazula?"</string>
@@ -128,4 +129,6 @@
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Hamba phansi/kwesokudla"</string>
<string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Bonisa i-app e-# ngaphezulu.}one{Bonisa ama-app angu-# ngaphezulu.}other{Bonisa ama-app angu-# ngaphezulu.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"I-<xliff:g id="APP_NAME_1">%1$s</xliff:g> ne-<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
+ <string name="desktop_select_app_toast" msgid="2306057322833956910">"Yengeza i-app ku-Deskithophu"</string>
+ <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Khansela"</string>
</resources>
diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml
index fb51919..7288774 100644
--- a/quickstep/res/values/attrs.xml
+++ b/quickstep/res/values/attrs.xml
@@ -26,7 +26,8 @@
-->
<declare-styleable name="TaskView">
<!-- Border color for a keyboard quick switch task views -->
- <attr name="borderColor" format="color" />
+ <attr name="focusBorderColor" format="color" />
+ <attr name="hoverBorderColor" format="color" />
</declare-styleable>
<!--
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 36c7352..1b5b0ee 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -25,6 +25,10 @@
<!-- Taskbar -->
<color name="taskbar_nav_icon_selection_ripple">#E0E0E0</color>
+ <color name="taskbar_nav_icon_light_color_on_home">#ffffff</color>
+ <!-- The dark navigation button color is only used in the rare cases that taskbar isn't drawing
+ its background and the underlying app has requested dark buttons. -->
+ <color name="taskbar_nav_icon_dark_color_on_home">#99000000</color>
<color name="taskbar_stashed_handle_light_color">#EBffffff</color>
<color name="taskbar_stashed_handle_dark_color">#99000000</color>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index bb4f74d..b024418 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -38,6 +38,8 @@
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<!-- The icon size for the focused task, placed in center of touch target -->
<dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
+ <!-- The border width shown when task is hovered -->
+ <dimen name="task_hover_border_width">4dp</dimen>
<!-- The space under the focused task icon -->
<dimen name="overview_task_margin">16dp</dimen>
<!-- The horizontal space between tasks -->
@@ -269,6 +271,9 @@
<dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
<dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
+ <!-- Copied from frameworks/base/packages/SystemUI -->
+ <dimen name="navigation_home_handle_width">108dp</dimen>
+
<!-- Taskbar -->
<dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
<dimen name="taskbar_ime_size">48dp</dimen>
@@ -323,6 +328,12 @@
<!-- Taskbar swipe down threshold -->
<dimen name="taskbar_to_nav_threshold">24dp</dimen>
+ <!-- Taskbar swipe up threshold multipliers -->
+ <item name="taskbar_nav_threshold_mult" format="float" type="dimen">4.5</item>
+ <item name="taskbar_app_window_threshold_mult" format="float" type="dimen">10</item>
+ <item name="taskbar_home_overview_threshold_mult" format="float" type="dimen">18</item>
+ <item name="taskbar_catch_up_threshold_mult" format="float" type="dimen">30</item>
+
<!-- Taskbar 3 button spacing -->
<dimen name="taskbar_button_space_inbetween">24dp</dimen>
<dimen name="taskbar_button_space_inbetween_phone">40dp</dimen>
@@ -356,13 +367,22 @@
<dimen name="bubblebar_stashed_size">@dimen/transient_taskbar_stashed_height</dimen>
<dimen name="bubblebar_stashed_handle_height">@dimen/taskbar_stashed_handle_height</dimen>
<dimen name="bubblebar_pointer_size">8dp</dimen>
+ <dimen name="bubblebar_elevation">1dp</dimen>
<dimen name="bubblebar_icon_size">50dp</dimen>
<dimen name="bubblebar_badge_size">24dp</dimen>
<dimen name="bubblebar_icon_overlap">12dp</dimen>
+ <dimen name="bubblebar_overflow_inset">24dp</dimen>
<dimen name="bubblebar_icon_spacing">3dp</dimen>
<dimen name="bubblebar_icon_elevation">1dp</dimen>
+ <!-- Bubble bar dismiss view -->
+ <dimen name="bubblebar_dismiss_target_size">96dp</dimen>
+ <dimen name="bubblebar_dismiss_target_small_size">60dp</dimen>
+ <dimen name="bubblebar_dismiss_target_icon_size">24dp</dimen>
+ <dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
+ <dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
+
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
<!-- starting_surface_exit_animation_window_shift_length -->
@@ -383,4 +403,12 @@
<dimen name="keyboard_quick_switch_task_view_radius">16dp</dimen>
<dimen name="keyboard_quick_switch_no_recent_items_icon_size">24dp</dimen>
<dimen name="keyboard_quick_switch_no_recent_items_icon_margin">8dp</dimen>
+
+ <!-- Desktop mode -->
+ <dimen name="desktop_mode_floating_app_select_height">56dp</dimen>
+ <dimen name="desktop_mode_floating_app_select_elevation">4dp</dimen>
+ <dimen name="desktop_mode_floating_app_select_margin">16dp</dimen>
+ <dimen name="desktop_mode_floating_app_select_text_size">14sp</dimen>
+ <dimen name="desktop_mode_floating_app_select_text_margin">8dp</dimen>
+
</resources>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index 4f472f0..df32626 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -25,6 +25,14 @@
<string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
+ <string name="nav_handle_long_press_handler_class" translatable="false"></string>
+
+ <string name="assist_utils_class" translatable="false"></string>
+
<string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
+ <string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
+
+ <string name="assist_state_manager_class" translatable="false"></string>
+
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 6d0dbae..dd9fc63 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -230,6 +230,7 @@
<string name="action_split">Split</string>
<!-- Label for toast with instructions for split screen selection mode. [CHAR_LIMIT=50] -->
<string name="toast_split_select_app">Tap another app to use split screen</string>
+ <string name="toast_split_select_cont_desc">Exit split screen selection</string>
<!-- Label for toast when app selected for split isn't supported. [CHAR_LIMIT=50] -->
<string name="toast_split_app_unsupported">Choose another app to use split screen</string>
<!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
@@ -308,4 +309,10 @@
<!-- Accessibility label for quick switch tiles showing split tasks [CHAR LIMIT=NONE] -->
<string name="quick_switch_split_task"><xliff:g id="app_name_1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app_name_2" example="Gmail">%2$s</xliff:g></string>
+
+ <!-- ******* Desktop ******* -->
+ <!-- Text shown in popup to choose a desktop app. [CHAR LIMIT=60] -->
+ <string name="desktop_select_app_toast">Adding app to Desktop</string>
+ <!-- Text shown on a button that closes the popup for choosing a desktop app. [CHAR_LIMIT=40] -->
+ <string name="desktop_button_close_app_toast">Cancel</string>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 21b7fd5..fc03704 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -298,4 +298,9 @@
<style name="rotate_prompt_subtitle" parent="TextAppearance.GestureTutorial.Dialog.Subtitle">
<item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
</style>
+
+ <style name="ArrowTipTaskbarStyle">
+ <item name="arrowTipBackground">?androidprv:attr/materialColorSurfaceContainer</item>
+ <item name="arrowTipTextColor">?androidprv:attr/materialColorOnSurface</item>
+ </style>
</resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 28bd701..f64b5cf 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -15,23 +15,16 @@
*/
package com.android.launcher3;
-import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.os.Build;
-import android.os.CancellationSignal;
-import android.view.RemoteAnimationTarget;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
import java.util.function.BiPredicate;
@TargetApi(Build.VERSION_CODES.P)
public class LauncherInitListener extends ActivityInitListener<Launcher> {
- private RemoteAnimationProvider mRemoteAnimationProvider;
-
/**
* @param onInitListener a callback made when the activity is initialized. The callback should
* return true to continue receiving callbacks (ie. for if the activity is
@@ -43,37 +36,7 @@
@Override
public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
- if (mRemoteAnimationProvider != null) {
- QuickstepTransitionManager appTransitionManager =
- ((QuickstepLauncher) launcher).getAppTransitionManager();
-
- // Set a one-time animation provider. After the first call, this will get cleared.
- // TODO: Probably also check the intended target id.
- CancellationSignal cancellationSignal = new CancellationSignal();
- appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
- @Override
- public AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
- RemoteAnimationTarget[] wallpaperTargets) {
-
- // On the first call clear the reference.
- cancellationSignal.cancel();
- RemoteAnimationProvider provider = mRemoteAnimationProvider;
- mRemoteAnimationProvider = null;
-
- if (provider != null && launcher.getStateManager().getState().overviewUi) {
- return provider.createWindowAnimation(appTargets, wallpaperTargets);
- }
- return null;
- }
- }, cancellationSignal);
- }
launcher.deferOverlayCallbacksUntilNextResumeOrStop();
return super.handleInit(launcher, alreadyOnHome);
}
-
- @Override
- public void unregister() {
- mRemoteAnimationProvider = null;
- super.unregister();
- }
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index f3e62c1..741249d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -31,6 +31,12 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
+import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
+import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE;
+import static com.android.app.animation.Interpolators.DECELERATE_1_5;
+import static com.android.app.animation.Interpolators.DECELERATE_1_7;
+import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -42,12 +48,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.mapBoundToRange;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_BACK_SWIPE_HOME_ANIMATION;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH;
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
@@ -60,6 +60,8 @@
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
+import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME;
+import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME_FALLBACK;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
@@ -82,7 +84,6 @@
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -140,7 +141,6 @@
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig;
-import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
@@ -151,6 +151,7 @@
import com.android.quickstep.views.RecentsView;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DelegateLaunchAnimatorController;
+import com.android.systemui.animation.LaunchableView;
import com.android.systemui.animation.RemoteAnimationDelegate;
import com.android.systemui.shared.system.BlurUtils;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -183,7 +184,7 @@
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
- private static final long APP_LAUNCH_DURATION = 500;
+ public static final long APP_LAUNCH_DURATION = 500;
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
@@ -229,7 +230,6 @@
private DeviceProfile mDeviceProfile;
- private RemoteAnimationProvider mRemoteAnimationProvider;
// Strong refs to runners which are cleared when the launcher activity is destroyed
private RemoteAnimationFactory mWallpaperOpenRunner;
private RemoteAnimationFactory mAppLaunchRunner;
@@ -286,7 +286,7 @@
mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
- R.interpolator.three_point_fast_out_extra_slow_in);
+ R.interpolator.emphasized_interpolator);
}
@Override
@@ -468,16 +468,6 @@
return bounds;
}
- public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
- CancellationSignal cancellationSignal) {
- mRemoteAnimationProvider = animationProvider;
- cancellationSignal.setOnCancelListener(() -> {
- if (animationProvider == mRemoteAnimationProvider) {
- mRemoteAnimationProvider = null;
- }
- });
- }
-
/** Dump debug logs to bug report. */
public void dump(@NonNull String prefix, @NonNull PrintWriter printWriter) {}
@@ -566,7 +556,7 @@
ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scales)
.setDuration(CONTENT_SCALE_DURATION);
- scaleAnim.setInterpolator(DEACCEL_1_5);
+ scaleAnim.setInterpolator(DECELERATE_1_5);
launcherAnimator.play(scaleAnim);
});
@@ -584,7 +574,7 @@
ObjectAnimator scrim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR,
colors);
scrim.setDuration(CONTENT_SCRIM_DURATION);
- scrim.setInterpolator(DEACCEL_1_5);
+ scrim.setInterpolator(DECELERATE_1_5);
launcherAnimator.play(scrim);
}
@@ -1234,7 +1224,7 @@
* ie. pressing home, swiping up from nav bar.
*/
RemoteAnimationFactory createWallpaperOpenRunner(boolean fromUnlock) {
- return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock);
+ return new WallpaperOpenLauncherAnimationRunner(fromUnlock);
}
/**
@@ -1352,7 +1342,7 @@
/**
* Closing animator that animates the window into its final location on the workspace.
*/
- private RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
+ protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,
RectF closingWindowStartRect, float startWindowCornerRadius) {
FloatingIconView floatingIconView = null;
@@ -1477,11 +1467,11 @@
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
- FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
- FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
+ FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DECELERATE_1_7);
+ FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DECELERATE_1_7);
FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration,
- DEACCEL_1_7);
+ DECELERATE_1_7);
@Override
public void onUpdate(float percent, boolean initOnly) {
@@ -1592,89 +1582,80 @@
RectF startRect,
float startWindowCornerRadius,
boolean fromPredictiveBack) {
- AnimatorSet anim = null;
+ AnimatorSet anim = new AnimatorSet();
RectFSpringAnim rectFSpringAnim = null;
- RemoteAnimationProvider provider = mRemoteAnimationProvider;
- if (provider != null) {
- anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
+ final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
+ || launcherIsATargetWithMode(appTargets, MODE_OPENING);
+
+ View launcherView = findLauncherView(appTargets);
+ boolean playFallBackAnimation = (launcherView == null
+ && launcherIsForceInvisibleOrOpening)
+ || mLauncher.getWorkspace().isOverlayShown()
+ || shouldPlayFallbackClosingAnimation(appTargets);
+
+ boolean playWorkspaceReveal = true;
+ boolean skipAllAppsScale = false;
+ if (fromUnlock) {
+ anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
+ } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
+ && !playFallBackAnimation) {
+ // Use a fixed velocity to start the animation.
+ float velocityPxPerS = DynamicResource.provider(mLauncher)
+ .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+ PointF velocity = new PointF(0, -velocityPxPerS);
+ rectFSpringAnim = getClosingWindowAnimators(
+ anim, appTargets, launcherView, velocity, startRect,
+ startWindowCornerRadius);
+ if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+ // Skip scaling all apps, otherwise FloatingIconView will get wrong
+ // layout bounds.
+ skipAllAppsScale = true;
+ } else if (!fromPredictiveBack) {
+ anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
+ true /* animateOverviewScrim */, launcherView).getAnimators());
+
+ if (!areAllTargetsTranslucent(appTargets)) {
+ anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
+ MULTI_PROPERTY_VALUE,
+ BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
+ }
+
+ // We play StaggeredWorkspaceAnim as a part of the closing window animation.
+ playWorkspaceReveal = false;
+ }
+ } else {
+ anim.play(getFallbackClosingWindowAnimators(appTargets));
}
- if (anim == null) {
- anim = new AnimatorSet();
+ // Normally, we run the launcher content animation when we are transitioning
+ // home, but if home is already visible, then we don't want to animate the
+ // contents of launcher unless we know that we are animating home as a result
+ // of the home button press with quickstep, which will result in launcher being
+ // started on touch down, prior to the animation home (and won't be in the
+ // targets list because it is already visible). In that case, we force
+ // invisibility on touch down, and only reset it after the animation to home
+ // is initialized.
+ if (launcherIsForceInvisibleOrOpening) {
+ addCujInstrumentation(anim, playFallBackAnimation
+ ? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
+ // Only register the content animation for cancellation when state changes
+ mLauncher.getStateManager().setCurrentAnimation(anim);
- final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
- || launcherIsATargetWithMode(appTargets, MODE_OPENING);
-
- View launcherView = findLauncherView(appTargets);
- boolean playFallBackAnimation = (launcherView == null
- && launcherIsForceInvisibleOrOpening)
- || mLauncher.getWorkspace().isOverlayShown()
- || shouldPlayFallbackClosingAnimation(appTargets);
-
- boolean playWorkspaceReveal = true;
- boolean skipAllAppsScale = false;
- if (fromUnlock) {
- anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
- } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
- && !playFallBackAnimation) {
- // Use a fixed velocity to start the animation.
- float velocityPxPerS = DynamicResource.provider(mLauncher)
- .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
- PointF velocity = new PointF(0, -velocityPxPerS);
- rectFSpringAnim = getClosingWindowAnimators(
- anim, appTargets, launcherView, velocity, startRect,
- startWindowCornerRadius);
- if (mLauncher.isInState(LauncherState.ALL_APPS)) {
- // Skip scaling all apps, otherwise FloatingIconView will get wrong
- // layout bounds.
- skipAllAppsScale = true;
- } else if (!fromPredictiveBack) {
- anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
- true /* animateOverviewScrim */, launcherView).getAnimators());
-
- if (!areAllTargetsTranslucent(appTargets)) {
- anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
- MULTI_PROPERTY_VALUE,
- BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
+ if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+ Pair<AnimatorSet, Runnable> contentAnimator =
+ getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
+ skipAllAppsScale);
+ anim.play(contentAnimator.first);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ contentAnimator.second.run();
}
-
- // We play StaggeredWorkspaceAnim as a part of the closing window animation.
- playWorkspaceReveal = false;
- }
+ });
} else {
- anim.play(getFallbackClosingWindowAnimators(appTargets));
- }
-
- // Normally, we run the launcher content animation when we are transitioning
- // home, but if home is already visible, then we don't want to animate the
- // contents of launcher unless we know that we are animating home as a result
- // of the home button press with quickstep, which will result in launcher being
- // started on touch down, prior to the animation home (and won't be in the
- // targets list because it is already visible). In that case, we force
- // invisibility on touch down, and only reset it after the animation to home
- // is initialized.
- if (launcherIsForceInvisibleOrOpening) {
- addCujInstrumentation(
- anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
- // Only register the content animation for cancellation when state changes
- mLauncher.getStateManager().setCurrentAnimation(anim);
-
- if (mLauncher.isInState(LauncherState.ALL_APPS)) {
- Pair<AnimatorSet, Runnable> contentAnimator =
- getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
- skipAllAppsScale);
- anim.play(contentAnimator.first);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- contentAnimator.second.run();
- }
- });
- } else {
- if (playWorkspaceReveal) {
- anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
- }
+ if (playWorkspaceReveal) {
+ anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
}
}
}
@@ -1687,11 +1668,9 @@
*/
protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory {
- private final Handler mHandler;
private final boolean mFromUnlock;
- public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) {
- mHandler = handler;
+ public WallpaperOpenLauncherAnimationRunner(boolean fromUnlock) {
mFromUnlock = fromUnlock;
}
@@ -1799,7 +1778,7 @@
@Nullable
private static ContainerAnimationRunner from(
View v, StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
- View viewToUse = findViewWithBackground(v);
+ View viewToUse = findLaunchableViewWithBackground(v);
if (viewToUse == null) {
viewToUse = v;
}
@@ -1837,19 +1816,23 @@
new ActivityLaunchAnimator.AnimationDelegate(controller, callback, listener));
}
- /** Finds the closest parent of [view] (inclusive) with a background drawable. */
+ /**
+ * Finds the closest parent of [view] (inclusive) that implements {@link LaunchableView} and
+ * has a background drawable.
+ */
@Nullable
- private static View findViewWithBackground(View view) {
+ private static <T extends View & LaunchableView> T findLaunchableViewWithBackground(
+ View view) {
View current = view;
- while (current.getBackground() == null) {
+ while (current.getBackground() == null || !(current instanceof LaunchableView)) {
if (!(current.getParent() instanceof View)) {
return null;
}
- current = (View) view.getParent();
+ current = (View) current.getParent();
}
- return current;
+ return (T) current;
}
@Override
@@ -2047,7 +2030,7 @@
if (progress >= end) {
return 0f;
}
- return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+ return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
}
}
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 34316db..f82474a 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -36,13 +36,11 @@
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
@@ -65,7 +63,6 @@
private FloatingHeaderView mParent;
private boolean mPredictionsEnabled = false;
- private OnLongClickListener mOnIconLongClickListener = ItemLongClickListener.INSTANCE_ALL_APPS;
public PredictionRowView(@NonNull Context context) {
this(context, null);
@@ -106,22 +103,12 @@
mActivityContext.getAppsView().getAppsStore().unregisterIconContainer(this);
}
}
-
- // Set the predicted row in All Apps' text line to 1.
- if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()
- || FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
- for (int i = 0; i < getChildCount(); i++) {
- BubbleTextView icon = (BubbleTextView) getChildAt(i);
- icon.setMaxLines(1);
- }
- }
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
MeasureSpec.EXACTLY));
- updateVisibility();
}
@Override
@@ -185,15 +172,6 @@
applyPredictionApps();
}
- /**
- * Sets the long click listener for predictions for any future predictions.
- *
- * Existing predictions in the container are not updated with this new callback.
- */
- public void setOnIconLongClickListener(OnLongClickListener onIconLongClickListener) {
- mOnIconLongClickListener = onIconLongClickListener;
- }
-
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mNumPredictedAppsPerRow = dp.numShownAllAppsColumns;
@@ -209,9 +187,9 @@
LayoutInflater inflater = mActivityContext.getAppsView().getLayoutInflater();
while (getChildCount() < mNumPredictedAppsPerRow) {
BubbleTextView icon = (BubbleTextView) inflater.inflate(
- R.layout.all_apps_icon, this, false);
+ R.layout.all_apps_prediction_row_icon, this, false);
icon.setOnClickListener(mActivityContext.getItemOnClickListener());
- icon.setOnLongClickListener(mOnIconLongClickListener);
+ icon.setOnLongClickListener(mActivityContext.getAllAppsItemLongClickListener());
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mFocusHelper);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 048243e..b77c43f 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -95,9 +95,7 @@
}
}
if (pageId == -1) {
- pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ pageId = mLauncher.getModel().getModelDbController().getNewScreenId();
mNewScreens = IntArray.wrap(pageId);
}
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 80bdb6f..db225be 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
-import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -37,7 +36,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.PredictedAppIcon;
@@ -160,14 +158,11 @@
}
private void animateOpen() {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
return;
}
mIsOpen = true;
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.start();
+ setUpDefaultOpenAnimation().start();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 85d0ab5..a63f9e8 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -22,11 +22,13 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.ComponentName;
+import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewGroup;
@@ -60,9 +62,11 @@
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.views.Snackbar;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.StringJoiner;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -74,6 +78,7 @@
SystemShortcut.Factory<QuickstepLauncher>, DeviceProfile.OnDeviceProfileChangeListener,
DragSource, ViewGroup.OnHierarchyChangeListener {
+ private static final String TAG = "HotseatPredictionController";
private static final int FLAG_UPDATE_PAUSED = 1 << 0;
private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1;
private static final int FLAG_FILL_IN_PROGRESS = 1 << 2;
@@ -114,8 +119,7 @@
WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
v.setVisibility(View.INVISIBLE);
mLauncher.getWorkspace().beginDragShared(
- v, null, this, dragItem, new DragPreviewProvider(v),
- mLauncher.getDefaultWorkspaceDragOptions());
+ v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
return true;
};
@@ -184,6 +188,7 @@
}
private void fillGapsWithPrediction(boolean animate) {
+ Log.d(TAG, "fillGapsWithPrediction flags: " + getStateString(mPauseFlags));
if (mPauseFlags != 0) {
return;
}
@@ -208,12 +213,16 @@
View child = mHotseat.getChildAt(
mHotseat.getCellXFromOrder(rank),
mHotseat.getCellYFromOrder(rank));
+ Log.d(TAG, "Hotseat app child is: " + child + " and isPredictedIcon() evaluates to"
+ + ": " + isPredictedIcon(child));
if (child != null && !isPredictedIcon(child)) {
continue;
}
if (mPredictedItems.size() <= predictionIndex) {
// Remove predicted apps from the past
+ Log.d(TAG, "Remove predicted apps from the past\nPrediction Index: "
+ + predictionIndex);
if (isPredictedIcon(child)) {
mHotseat.removeView(child);
}
@@ -221,6 +230,11 @@
}
WorkspaceItemInfo predictedItem =
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
+ Log.d(TAG, "Predicted item is: " + predictedItem);
+ if (child != null) {
+ Log.d(TAG, "Predicted item is enabled: " + child.isEnabled());
+ }
+
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
@@ -240,6 +254,7 @@
}
private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate) {
+ Log.d(TAG, "bindItems to hotseat: " + itemsToAdd);
AnimatorSet animationSet = new AnimatorSet();
for (WorkspaceItemInfo item : itemsToAdd) {
PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
@@ -279,6 +294,7 @@
* start and pauses predicted apps update on the hotseat
*/
public void setPauseUIUpdate(boolean paused) {
+ Log.d(TAG, "setPauseUIUpdate parameter `paused` is " + paused);
mPauseFlags = paused
? (mPauseFlags | FLAG_UPDATE_PAUSED)
: (mPauseFlags & ~FLAG_UPDATE_PAUSED);
@@ -293,8 +309,10 @@
public void setPredictedItems(FixedContainerItems items) {
mPredictedItems = new ArrayList(items.items);
if (mPredictedItems.isEmpty()) {
+ Log.d(TAG, "Predicted items is initially empty");
HotseatRestoreHelper.restoreBackup(mLauncher);
}
+ Log.d(TAG, "Predicted items: " + mPredictedItems);
fillGapsWithPrediction();
}
@@ -501,4 +519,21 @@
&& ((WorkspaceItemInfo) view.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
+
+ private static String getStateString(int flags) {
+ StringJoiner str = new StringJoiner("|");
+ appendFlag(str, flags, FLAG_UPDATE_PAUSED, "FLAG_UPDATE_PAUSED");
+ appendFlag(str, flags, FLAG_DRAG_IN_PROGRESS, "FLAG_DRAG_IN_PROGRESS");
+ appendFlag(str, flags, FLAG_FILL_IN_PROGRESS, "FLAG_FILL_IN_PROGRESS");
+ appendFlag(str, flags, FLAG_REMOVING_PREDICTED_ICON,
+ "FLAG_REMOVING_PREDICTED_ICON");
+ return str.toString();
+ }
+
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
+ writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
+ writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
index 726abff..8c01d04 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -22,9 +22,10 @@
import android.content.Context;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.model.GridBackupTable;
-import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
/**
* A helper class to manage migration revert restoration for hybrid hotseat
@@ -36,16 +37,13 @@
*/
public static void createBackup(Context context) {
MODEL_EXECUTOR.execute(() -> {
- try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
- LauncherSettings.Settings.call(
- context.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
- .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+ ModelDbController dbController = LauncherAppState.getInstance(context)
+ .getModel().getModelDbController();
+ try (SQLiteTransaction transaction = dbController.newTransaction()) {
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
transaction.commit();
- LauncherSettings.Settings.call(context.getContentResolver(),
- LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
+ dbController.refreshHotseatRestoreTable();
}
});
}
@@ -55,18 +53,15 @@
*/
public static void restoreBackup(Context context) {
MODEL_EXECUTOR.execute(() -> {
- try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
- LauncherSettings.Settings.call(
- context.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
- .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+ LauncherModel model = LauncherAppState.getInstance(context).getModel();
+ try (SQLiteTransaction transaction = model.getModelDbController().newTransaction()) {
if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
return;
}
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
transaction.commit();
- LauncherAppState.getInstance(context).getModel().forceReload();
+ model.forceReload();
}
});
}
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index bc97df1..32361a8 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -52,6 +52,7 @@
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
@@ -93,11 +94,14 @@
private static final boolean IS_DEBUG = false;
private static final String TAG = "QuickstepModelDelegate";
- private final PredictorState mAllAppsState =
+ @VisibleForTesting
+ final PredictorState mAllAppsState =
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
- private final PredictorState mHotseatState =
+ @VisibleForTesting
+ final PredictorState mHotseatState =
new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
- private final PredictorState mWidgetsRecommendationState =
+ @VisibleForTesting
+ final PredictorState mWidgetsRecommendationState =
new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction");
private final InvariantDeviceProfile mIDP;
@@ -348,12 +352,7 @@
.build()));
// TODO: get bundle
- registerPredictor(mHotseatState, apm.createAppPredictionSession(
- new AppPredictionContext.Builder(context)
- .setUiSurface("hotseat")
- .setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
- .setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
- .build()));
+ registerHotseatPredictor(apm, context);
registerWidgetsPredictor(apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
@@ -363,6 +362,29 @@
.build()));
}
+ @WorkerThread
+ private void recreateHotseatPredictor() {
+ mHotseatState.destroyPredictor();
+ if (!mActive) {
+ return;
+ }
+ Context context = mApp.getContext();
+ AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+ if (apm == null) {
+ return;
+ }
+ registerHotseatPredictor(apm, context);
+ }
+
+ private void registerHotseatPredictor(AppPredictionManager apm, Context context) {
+ registerPredictor(mHotseatState, apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(context)
+ .setUiSurface("hotseat")
+ .setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
+ .setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
+ .build()));
+ }
+
private void registerPredictor(PredictorState state, AppPredictor predictor) {
state.setTargets(Collections.emptyList());
state.predictor = predictor;
@@ -393,7 +415,8 @@
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
}
- private void onAppTargetEvent(AppTargetEvent event, int client) {
+ @VisibleForTesting
+ void onAppTargetEvent(AppTargetEvent event, int client) {
PredictorState state;
switch(client) {
case CONTAINER_PREDICTION:
@@ -411,6 +434,13 @@
state.predictor.notifyAppTargetEvent(event);
Log.d(TAG, "notifyAppTargetEvent action=" + event.getAction()
+ " launchLocation=" + event.getLaunchLocation());
+ if (state == mHotseatState
+ && (event.getAction() == AppTargetEvent.ACTION_PIN
+ || event.getAction() == AppTargetEvent.ACTION_UNPIN)) {
+ // Recreate hot seat predictor when we need to query for hot seat due to pin or
+ // unpin app icons.
+ recreateHotseatPredictor();
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
index 8720bd8..b982688 100644
--- a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
+++ b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
@@ -18,9 +18,7 @@
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
import android.content.Context;
-import android.view.View;
-import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.appprediction.AppsDividerView;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.model.BgDataModel;
@@ -56,12 +54,4 @@
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(item.items);
}
-
- @Override
- public void setLongClickListener(ActivityAllAppsContainerView<?> appsView,
- View.OnLongClickListener onIconLongClickListener) {
- appsView.getFloatingHeaderView()
- .findFixedRowByType(PredictionRowView.class)
- .setOnIconLongClickListener(onIconLongClickListener);
- }
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 70aa5d7..882682d 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -16,7 +16,7 @@
package com.android.launcher3.statehandlers;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
@@ -193,5 +193,6 @@
writer.println(prefix + "\tmIgnoreStateChangesDuringMultiWindowAnimation="
+ mIgnoreStateChangesDuringMultiWindowAnimation);
writer.println(prefix + "\tmPauseBlurs=" + mPauseBlurs);
+ writer.println(prefix + "\tmWaitingOnSurfaceValidity=" + mWaitingOnSurfaceValidity);
}
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index d087d39..ecf483c 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -15,14 +15,22 @@
*/
package com.android.launcher3.statehandlers;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.os.SystemProperties;
import android.util.Log;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.DesktopAppSelectView;
+import com.android.wm.shell.desktopmode.IDesktopTaskListener;
/**
* Controls the visibility of the workspace and the resumed / paused state when desktop mode
@@ -32,18 +40,69 @@
private static final String TAG = "DesktopVisController";
private static final boolean DEBUG = false;
-
+ private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_stashing", false);
private final Launcher mLauncher;
private boolean mFreeformTasksVisible;
private boolean mInOverviewState;
private boolean mGestureInProgress;
+ @Nullable
+ private IDesktopTaskListener mDesktopTaskListener;
+ private DesktopAppSelectView mSelectAppToast;
+
public DesktopVisibilityController(Launcher launcher) {
mLauncher = launcher;
}
/**
+ * Register a listener with System UI to receive updates about desktop tasks state
+ */
+ public void registerSystemUiListener() {
+ mDesktopTaskListener = new IDesktopTaskListener.Stub() {
+ @Override
+ public void onVisibilityChanged(int displayId, boolean visible) {
+ MAIN_EXECUTOR.execute(() -> {
+ if (displayId == mLauncher.getDisplayId()) {
+ if (DEBUG) {
+ Log.d(TAG, "desktop visibility changed value=" + visible);
+ }
+ setFreeformTasksVisible(visible);
+ }
+ });
+ }
+
+ @Override
+ public void onStashedChanged(int displayId, boolean stashed) {
+ if (!IS_STASHING_ENABLED) {
+ return;
+ }
+ MAIN_EXECUTOR.execute(() -> {
+ if (displayId == mLauncher.getDisplayId()) {
+ if (DEBUG) {
+ Log.d(TAG, "desktop stashed changed value=" + stashed);
+ }
+ if (stashed) {
+ showSelectAppToast();
+ } else {
+ hideSelectAppToast();
+ }
+ }
+ });
+ }
+ };
+ SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener);
+ }
+
+ /**
+ * Clear listener from System UI that was set with {@link #registerSystemUiListener()}
+ */
+ public void unregisterSystemUiListener() {
+ SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null);
+ }
+
+ /**
* Whether desktop mode is supported.
*/
private boolean isDesktopModeSupported() {
@@ -68,6 +127,7 @@
if (!isDesktopModeSupported()) {
return;
}
+
if (freeformTasksVisible != mFreeformTasksVisible) {
mFreeformTasksVisible = freeformTasksVisible;
if (mFreeformTasksVisible) {
@@ -111,25 +171,54 @@
/**
* Whether recents gesture is currently in progress.
*/
- public boolean isGestureInProgress() {
+ public boolean isRecentsGestureInProgress() {
return mGestureInProgress;
}
/**
- * Sets whether recents gesture is in progress.
+ * Notify controller that recents gesture has started.
*/
- public void setGestureInProgress(boolean gestureInProgress) {
- if (DEBUG) {
- Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
- }
+ public void setRecentsGestureStart() {
if (!isDesktopModeSupported()) {
return;
}
+ setRecentsGestureInProgress(true);
+ }
+
+ /**
+ * Notify controller that recents gesture finished with the given
+ * {@link com.android.quickstep.GestureState.GestureEndTarget}
+ */
+ public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) {
+ if (!isDesktopModeSupported()) {
+ return;
+ }
+ setRecentsGestureInProgress(false);
+
+ if (endTarget == null) {
+ // Gesture did not result in a new end target. Ensure launchers gets paused again.
+ markLauncherPaused();
+ }
+ }
+
+ private void setRecentsGestureInProgress(boolean gestureInProgress) {
+ if (DEBUG) {
+ Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
+ }
if (gestureInProgress != mGestureInProgress) {
mGestureInProgress = gestureInProgress;
}
}
+ /**
+ * Handle launcher moving to home due to home gesture or home button press.
+ */
+ public void onHomeActionTriggered() {
+ if (IS_STASHING_ENABLED && areFreeformTasksVisible()) {
+ SystemUiProxy.INSTANCE.get(mLauncher).stashDesktopApps(mLauncher.getDisplayId());
+ }
+ }
+
private void setLauncherViewsVisibility(int visibility) {
if (DEBUG) {
Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
@@ -168,4 +257,28 @@
activity.setResumed();
}
}
+
+ private void showSelectAppToast() {
+ if (mSelectAppToast != null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "show toast to select desktop apps");
+ }
+ Runnable onCloseCallback = () -> {
+ SystemUiProxy.INSTANCE.get(mLauncher).hideStashedDesktopApps(mLauncher.getDisplayId());
+ };
+ mSelectAppToast = DesktopAppSelectView.show(mLauncher, onCloseCallback);
+ }
+
+ private void hideSelectAppToast() {
+ if (mSelectAppToast == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "hide toast to select desktop apps");
+ }
+ mSelectAppToast.hide();
+ mSelectAppToast = null;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 7f655cf..072fc30 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -22,9 +22,13 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsModel;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -102,21 +106,74 @@
mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
+ DesktopVisibilityController desktopController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ final boolean onDesktop =
+ DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
+ && desktopController != null
+ && desktopController.areFreeformTasksVisible();
+
if (mModel.isTaskListValid(mTaskListChangeId)) {
- mQuickSwitchViewController.openQuickSwitchView(
- mTasks, mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex);
+ mQuickSwitchViewController.openQuickSwitchView(mTasks,
+ mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex, onDesktop);
return;
}
+
mTaskListChangeId = mModel.getTasks((tasks) -> {
- // Only store MAX_TASK tasks, from most to least recent
- Collections.reverse(tasks);
- mTasks = tasks.stream().limit(MAX_TASKS).collect(Collectors.toList());
- mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
- mQuickSwitchViewController.openQuickSwitchView(
- mTasks, mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex);
+ if (onDesktop) {
+ processLoadedTasksOnDesktop(tasks);
+ } else {
+ processLoadedTasks(tasks);
+ }
+ mQuickSwitchViewController.openQuickSwitchView(mTasks,
+ mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex, onDesktop);
});
}
+ private void processLoadedTasks(ArrayList<GroupTask> tasks) {
+ // Only store MAX_TASK tasks, from most to least recent
+ Collections.reverse(tasks);
+
+ // Hide all desktop tasks and show them on the hidden tile
+ int hiddenDesktopTasks = 0;
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ DesktopTask desktopTask = findDesktopTask(tasks);
+ if (desktopTask != null) {
+ hiddenDesktopTasks = desktopTask.tasks.size();
+ tasks = tasks.stream()
+ .filter(t -> !(t instanceof DesktopTask))
+ .collect(Collectors.toCollection(ArrayList<GroupTask>::new));
+ }
+ }
+ mTasks = tasks.stream()
+ .limit(MAX_TASKS)
+ .collect(Collectors.toList());
+ mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS) + hiddenDesktopTasks;
+ }
+
+ private void processLoadedTasksOnDesktop(ArrayList<GroupTask> tasks) {
+ // Find the single desktop task that contains a grouping of desktop tasks
+ DesktopTask desktopTask = findDesktopTask(tasks);
+
+ if (desktopTask != null) {
+ mTasks = desktopTask.tasks.stream().map(GroupTask::new).collect(Collectors.toList());
+ // All other tasks, apart from the grouped desktop task, are hidden
+ mNumHiddenTasks = Math.max(0, tasks.size() - 1);
+ } else {
+ // Desktop tasks were visible, but the recents entry is missing. Fall back to empty list
+ mTasks = Collections.emptyList();
+ mNumHiddenTasks = tasks.size();
+ }
+ }
+
+ @Nullable
+ private DesktopTask findDesktopTask(ArrayList<GroupTask> tasks) {
+ return (DesktopTask) tasks.stream()
+ .filter(t -> t instanceof DesktopTask)
+ .findFirst()
+ .orElse(null);
+ }
+
void closeQuickSwitchView() {
if (mQuickSwitchViewController == null) {
return;
@@ -169,7 +226,7 @@
class ControllerCallbacks {
int getTaskCount() {
- return mNumHiddenTasks == 0 ? mTasks.size() : MAX_TASKS + 1;
+ return mTasks.size() + (mNumHiddenTasks == 0 ? 0 : 1);
}
@Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 8a11b57..3e1a6ae 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -80,7 +80,7 @@
setWillNotDraw(false);
mBorderColor = ta.getColor(
- R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR);
+ R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR);
ta.recycle();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 2cdfb18..4e9e301 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -42,10 +42,10 @@
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.util.GroupTask;
import java.util.HashMap;
@@ -190,8 +190,12 @@
ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
width, mTaskViewHeight);
- lp.endToEnd = PARENT_ID;
- lp.startToEnd = previousView.getId();
+ if (previousView == null) {
+ lp.startToStart = PARENT_ID;
+ } else {
+ lp.endToEnd = PARENT_ID;
+ lp.startToEnd = previousView.getId();
+ }
lp.topToTop = PARENT_ID;
lp.bottomToBottom = PARENT_ID;
lp.setMarginEnd(mSpacing);
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 3230c66..a293f74 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -27,6 +27,7 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -52,6 +53,8 @@
private int mCurrentFocusIndex = -1;
+ private boolean mOnDesktop;
+
protected KeyboardQuickSwitchViewController(
@NonNull TaskbarControllers controllers,
@NonNull TaskbarOverlayContext overlayContext,
@@ -71,10 +74,12 @@
@NonNull List<GroupTask> tasks,
int numHiddenTasks,
boolean updateTasks,
- int currentFocusIndexOverride) {
+ int currentFocusIndexOverride,
+ boolean onDesktop) {
TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer();
dragLayer.addView(mKeyboardQuickSwitchView);
dragLayer.runOnClickOnce(v -> closeQuickSwitchView(true));
+ mOnDesktop = onDesktop;
mKeyboardQuickSwitchView.applyLoadPlan(
mOverlayContext,
@@ -136,6 +141,10 @@
GroupTask task = mControllerCallbacks.getTaskAt(index);
if (task == null) {
return Math.max(0, index);
+ } else if (mOnDesktop) {
+ UI_HELPER_EXECUTOR.execute(() ->
+ SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
+ .showDesktopApp(task.task1.key.id));
} else if (task.task2 == null) {
UI_HELPER_EXECUTOR.execute(() ->
ActivityManagerWrapper.getInstance().startActivityFromRecents(
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index abf49eb..4e834ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -317,6 +317,8 @@
.getTaskbarNavButtonTranslationYForInAppDisplay()
.updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
* mTaskbarInAppDisplayProgress.value);
+ mControllers.navbarButtonsViewController
+ .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 9c463cb..fcd8c80 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -88,6 +88,7 @@
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
import com.android.launcher3.util.DimensionUtils;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.TouchController;
@@ -148,8 +149,8 @@
// Used for IME+A11Y buttons
private final ViewGroup mEndContextualContainer;
private final ViewGroup mStartContextualContainer;
- private final int mLightIconColor;
- private final int mDarkIconColor;
+ private final int mLightIconColorOnHome;
+ private final int mDarkIconColorOnHome;
/** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */
private final int mOnBackgroundIconColor;
@@ -197,6 +198,7 @@
this::onComputeInsetsForSeparateWindow;
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
private ImageView mRecentsButton;
+ private DisplayController mDisplayController;
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
mContext = context;
@@ -205,9 +207,11 @@
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
- mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
- mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
- mOnBackgroundIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
+ mLightIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_light_color_on_home);
+ mDarkIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_dark_color_on_home);
+ mOnBackgroundIconColor = Utilities.isDarkTheme(context)
+ ? context.getColor(R.color.taskbar_nav_icon_light_color)
+ : context.getColor(R.color.taskbar_nav_icon_dark_color);
}
/**
@@ -224,6 +228,8 @@
TaskbarManager.isPhoneMode(deviceProfile));
mNavButtonsView.getLayoutParams().height = p.y;
+ mDisplayController = DisplayController.INSTANCE.get(mContext);
+
mIsImeRenderingNavButtons =
InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
if (!mIsImeRenderingNavButtons) {
@@ -630,18 +636,20 @@
private void updateNavButtonColor() {
final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
- final int sysUiNavButtonIconColor = (int) argbEvaluator.evaluate(
+ final int sysUiNavButtonIconColorOnHome = (int) argbEvaluator.evaluate(
mTaskbarNavButtonDarkIntensity.value,
- mLightIconColor,
- mDarkIconColor);
+ mLightIconColorOnHome,
+ mDarkIconColorOnHome);
+
// Override the color from framework if nav buttons are over an opaque Taskbar surface.
final int iconColor = (int) argbEvaluator.evaluate(
mOnBackgroundNavButtonColorOverrideMultiplier.value
* Math.max(
mOnTaskbarBackgroundNavButtonColorOverride.value,
mSlideInViewVisibleNavButtonColorOverride.value),
- sysUiNavButtonIconColor,
+ sysUiNavButtonIconColorOnHome,
mOnBackgroundIconColor);
+
for (ImageView button : mAllButtons) {
button.setImageTintList(ColorStateList.valueOf(iconColor));
}
@@ -723,15 +731,12 @@
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
- if (!isThreeButtonNav) {
- return;
- }
-
NavButtonLayoutter navButtonLayoutter =
NavButtonLayoutFactory.Companion.getUiLayoutter(
dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
- TaskbarManager.isPhoneMode(dp));
+ TaskbarManager.isPhoneMode(dp), mDisplayController.getInfo().rotation);
navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
+ updateNavButtonColor();
return;
}
@@ -937,8 +942,6 @@
pw.println(prefix + "NavbarButtonsViewController:");
pw.println(prefix + "\tmState=" + getStateString(mState));
- pw.println(prefix + "\tmLightIconColor=" + Integer.toHexString(mLightIconColor));
- pw.println(prefix + "\tmDarkIconColor=" + Integer.toHexString(mDarkIconColor));
pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds);
pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString(
mSysuiStateFlags));
@@ -949,6 +952,14 @@
+ mTaskbarNavButtonTranslationYForInAppDisplay.value);
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
+ mTaskbarNavButtonTranslationYForIme.value);
+ pw.println(prefix + "\t\tmTaskbarNavButtonDarkIntensity="
+ + mTaskbarNavButtonDarkIntensity.value);
+ pw.println(prefix + "\t\tmSlideInViewVisibleNavButtonColorOverride="
+ + mSlideInViewVisibleNavButtonColorOverride.value);
+ pw.println(prefix + "\t\tmOnTaskbarBackgroundNavButtonColorOverride="
+ + mOnTaskbarBackgroundNavButtonColorOverride.value);
+ pw.println(prefix + "\t\tmOnBackgroundNavButtonColorOverrideMultiplier="
+ + mOnBackgroundNavButtonColorOverrideMultiplier.value);
}
private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index a1390ae..b15dda2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -49,14 +49,15 @@
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Process;
-import android.os.SystemProperties;
import android.os.Trace;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.RoundedCorner;
+import android.view.Surface;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;
@@ -90,6 +91,8 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
+import com.android.launcher3.taskbar.bubbles.BubbleDragController;
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
@@ -117,6 +120,7 @@
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
+import java.util.Collections;
import java.util.Optional;
/**
@@ -128,8 +132,6 @@
private static final String IME_DRAWS_IME_NAV_BAR_RES_NAME = "config_imeDrawsImeNavBar";
- private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
- SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
private static final String TAG = "TaskbarActivityContext";
private static final String WINDOW_TITLE = "Taskbar";
@@ -146,7 +148,7 @@
private int mLastRequestedNonFullscreenHeight;
private NavigationMode mNavMode;
- private final boolean mImeDrawsImeNavBar;
+ private boolean mImeDrawsImeNavBar;
private final ViewCache mViewCache = new ViewCache();
private final boolean mIsSafeModeEnabled;
@@ -169,30 +171,27 @@
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
unfoldTransitionProgressProvider) {
super(windowContext);
+
+ applyDeviceProfile(launcherDp);
+
final Resources resources = getResources();
- matchDeviceProfile(launcherDp, getResources());
-
- mNavMode = DisplayController.getNavigationMode(windowContext);
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
+
+ // TODO(b/244231596) For shared Taskbar window, update this value in applyDeviceProfile()
+ // instead so to get correct value when recreating the taskbar
SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
mIsUserSetupComplete = settingsCache.getValue(
Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
- mIsNavBarForceVisible = settingsCache.getValue(
- Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
-
- // TODO(b/244231596) For shared Taskbar window, update this value in init() instead so
- // to get correct value when recreating the taskbar
mIsNavBarKidsMode = settingsCache.getValue(
Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
+ mIsNavBarForceVisible = mIsNavBarKidsMode;
// Get display and corners first, as views might use them in constructor.
Display display = windowContext.getDisplay();
- Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
- ? windowContext.getApplicationContext()
- : windowContext.getApplicationContext().createDisplayContext(display);
+ Context c = getApplicationContext();
mWindowManager = c.getSystemService(WindowManager.class);
mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
@@ -216,29 +215,34 @@
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
- if (BubbleBarController.BUBBLE_BAR_ENABLED) {
+ if (BubbleBarController.BUBBLE_BAR_ENABLED && bubbleBarView != null) {
bubbleControllersOptional = Optional.of(new BubbleControllers(
new BubbleBarController(this, bubbleBarView),
new BubbleBarViewController(this, bubbleBarView),
new BubbleStashController(this),
- new BubbleStashedHandleViewController(this, bubbleHandleView)));
+ new BubbleStashedHandleViewController(this, bubbleHandleView),
+ new BubbleDragController(this),
+ new BubbleDismissController(this, mDragLayer)));
}
// Construct controllers.
+ RotationButtonController rotationButtonController = new RotationButtonController(this,
+ c.getColor(R.color.floating_rotation_button_light_color),
+ c.getColor(R.color.floating_rotation_button_dark_color),
+ R.drawable.ic_sysbar_rotate_button_ccw_start_0,
+ R.drawable.ic_sysbar_rotate_button_ccw_start_90,
+ R.drawable.ic_sysbar_rotate_button_cw_start_0,
+ R.drawable.ic_sysbar_rotate_button_cw_start_90,
+ () -> getDisplay().getRotation());
+ rotationButtonController.setBgExecutor(Executors.THREAD_POOL_EXECUTOR);
+
mControllers = new TaskbarControllers(this,
new TaskbarDragController(this),
buttonController,
isDesktopMode
? new DesktopNavbarButtonsViewController(this, navButtonsView)
: new NavbarButtonsViewController(this, navButtonsView),
- new RotationButtonController(this,
- c.getColor(R.color.floating_rotation_button_light_color),
- c.getColor(R.color.floating_rotation_button_dark_color),
- R.drawable.ic_sysbar_rotate_button_ccw_start_0,
- R.drawable.ic_sysbar_rotate_button_ccw_start_90,
- R.drawable.ic_sysbar_rotate_button_cw_start_0,
- R.drawable.ic_sysbar_rotate_button_cw_start_90,
- () -> getDisplay().getRotation()),
+ rotationButtonController,
new TaskbarDragLayerController(this, mDragLayer),
new TaskbarViewController(this, taskbarView),
new TaskbarScrimViewController(this, taskbarScrimView),
@@ -267,13 +271,48 @@
bubbleControllersOptional);
}
+ /** Updates {@link DeviceProfile} instances for any Taskbar windows. */
+ public void updateDeviceProfile(DeviceProfile launcherDp) {
+ applyDeviceProfile(launcherDp);
+
+ mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp);
+ AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
+ // Reapply fullscreen to take potential new screen size into account.
+ setTaskbarWindowFullscreen(mIsFullscreen);
+
+ dispatchDeviceProfileChanged();
+ }
+
+ /**
+ * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
+ * the icon size
+ */
+ private void applyDeviceProfile(DeviceProfile originDeviceProfile) {
+ mDeviceProfile = originDeviceProfile.toBuilder(this)
+ .withDimensionsOverride(deviceProfile -> {
+ // Taskbar should match the number of icons of hotseat
+ deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
+ // Same QSB width to have a smooth animation
+ deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
+
+ // Update icon size
+ deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
+ deviceProfile.updateIconSize(1f, getResources());
+ }).build();
+ mNavMode = DisplayController.getNavigationMode(this);
+ }
+
+
public void init(@NonNull TaskbarSharedState sharedState) {
+ mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
- mWindowLayoutParams =
- createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL, WINDOW_TITLE);
+ mWindowLayoutParams = createAllWindowParams();
// Initialize controllers after all are constructed.
mControllers.init(sharedState);
+ // This may not be necessary and can be reverted once we move towards recreating all
+ // controllers without re-creating the window
+ mControllers.rotationButtonController.onNavigationModeChanged(mNavMode.resValue);
updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
disableNavBarElements(sharedState.disableNavBarDisplayId, sharedState.disableNavBarState1,
sharedState.disableNavBarState2, false /* animate */);
@@ -303,24 +342,16 @@
mControllers.taskbarStashController.showTaskbarFromBroadcast();
}
+ /** Toggles Taskbar All Apps overlay. */
+ public void toggleAllApps() {
+ mControllers.taskbarAllAppsController.toggle();
+ }
+
@Override
public DeviceProfile getDeviceProfile() {
return mDeviceProfile;
}
- /** Updates {@link DeviceProfile} instances for any Taskbar windows. */
- public void updateDeviceProfile(DeviceProfile launcherDp, NavigationMode navMode) {
- mNavMode = navMode;
- mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp);
- matchDeviceProfile(launcherDp, getResources());
-
- AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
- // Reapply fullscreen to take potential new screen size into account.
- setTaskbarWindowFullscreen(mIsFullscreen);
-
- dispatchDeviceProfileChanged();
- }
-
@Override
public void dispatchDeviceProfileChanged() {
super.dispatchDeviceProfileChanged();
@@ -329,24 +360,6 @@
}
/**
- * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
- * the icon size
- */
- private void matchDeviceProfile(DeviceProfile originDeviceProfile, Resources resources) {
- mDeviceProfile = originDeviceProfile.toBuilder(this)
- .withDimensionsOverride(deviceProfile -> {
- // Taskbar should match the number of icons of hotseat
- deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
- // Same QSB width to have a smooth animation
- deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
-
- // Update icon size
- deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
- deviceProfile.updateIconSize(1f, resources);
- }).build();
- }
-
- /**
* Returns the View bounds of transient taskbar.
*/
public Rect getTransientTaskbarBounds() {
@@ -365,11 +378,6 @@
* @param title The window title to pass to the created WindowManager.LayoutParams.
*/
public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
- DeviceProfile deviceProfile = getDeviceProfile();
- // Taskbar is on the logical bottom of the screen
- boolean isVerticalBarLayout = TaskbarManager.isPhoneButtonNavMode(this) &&
- deviceProfile.isLandscape;
-
int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SLIPPERY
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
@@ -378,17 +386,14 @@
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
}
WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
- isVerticalBarLayout ? mLastRequestedNonFullscreenHeight : MATCH_PARENT,
- isVerticalBarLayout ? MATCH_PARENT : mLastRequestedNonFullscreenHeight,
+ MATCH_PARENT,
+ mLastRequestedNonFullscreenHeight,
type,
windowFlags,
PixelFormat.TRANSLUCENT);
windowLayoutParams.setTitle(title);
windowLayoutParams.packageName = getPackageName();
- windowLayoutParams.gravity = !isVerticalBarLayout ?
- Gravity.BOTTOM :
- Gravity.END; // TODO(b/230394142): seascape
-
+ windowLayoutParams.gravity = Gravity.BOTTOM;
windowLayoutParams.setFitInsetsTypes(0);
windowLayoutParams.receiveInsetsIgnoringZOrder = true;
windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -399,6 +404,64 @@
TaskbarManager.isPhoneMode(mDeviceProfile)
? R.string.taskbar_phone_a11y_title
: R.string.taskbar_a11y_title);
+
+ return windowLayoutParams;
+ }
+
+ /**
+ * Creates {@link WindowManager.LayoutParams} for Taskbar, and also sets LP.paramsForRotation
+ * for taskbar showing as navigation bar
+ */
+ private WindowManager.LayoutParams createAllWindowParams() {
+ WindowManager.LayoutParams windowLayoutParams =
+ createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
+ TaskbarActivityContext.WINDOW_TITLE);
+ boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
+ if (!isPhoneNavMode) {
+ return windowLayoutParams;
+ }
+
+ // Provide WM layout params for all rotations to cache, see NavigationBar#getBarLayoutParams
+ int width = WindowManager.LayoutParams.MATCH_PARENT;
+ int height = WindowManager.LayoutParams.MATCH_PARENT;
+ int gravity = Gravity.BOTTOM;
+ windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4];
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ WindowManager.LayoutParams lp =
+ createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
+ TaskbarActivityContext.WINDOW_TITLE);
+ switch (rot) {
+ case Surface.ROTATION_0, Surface.ROTATION_180 -> {
+ // Defaults are fine
+ width = WindowManager.LayoutParams.MATCH_PARENT;
+ height = mLastRequestedNonFullscreenHeight;
+ gravity = Gravity.BOTTOM;
+ }
+ case Surface.ROTATION_90 -> {
+ width = mLastRequestedNonFullscreenHeight;
+ height = WindowManager.LayoutParams.MATCH_PARENT;
+ gravity = Gravity.END;
+ }
+ case Surface.ROTATION_270 -> {
+ width = mLastRequestedNonFullscreenHeight;
+ height = WindowManager.LayoutParams.MATCH_PARENT;
+ gravity = Gravity.START;
+ }
+
+ }
+ lp.width = width;
+ lp.height = height;
+ lp.gravity = gravity;
+ windowLayoutParams.paramsForRotation[rot] = lp;
+ }
+
+ // Override current layout params
+ WindowManager.LayoutParams currentParams =
+ windowLayoutParams.paramsForRotation[getDisplay().getRotation()];
+ windowLayoutParams.width = currentParams.width;
+ windowLayoutParams.height = currentParams.height;
+ windowLayoutParams.gravity = currentParams.gravity;
+
return windowLayoutParams;
}
@@ -448,6 +511,11 @@
return mControllers.taskbarDragController;
}
+ @Nullable
+ public BubbleControllers getBubbleControllers() {
+ return mControllers.bubbleControllers.orElse(null);
+ }
+
@Override
public ViewCache getViewCache() {
return mViewCache;
@@ -579,6 +647,8 @@
Executors.MAIN_EXECUTOR.getHandler(), null,
elapsedRealTime -> callbacks.executeAllAndDestroy());
options.setSplashScreenStyle(splashScreenStyle);
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
return new ActivityOptionsWrapper(options, callbacks);
}
@@ -638,8 +708,12 @@
mControllers.taskbarForceVisibleImmersiveController.updateSysuiFlags(systemUiStateFlags);
mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible(
(systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit);
-
mControllers.uiController.updateStateForSysuiFlags(systemUiStateFlags);
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ controllers.bubbleBarController.updateStateForSysuiFlags(systemUiStateFlags);
+ controllers.bubbleStashedHandleViewController.setIsHomeButtonDisabled(
+ mControllers.navbarButtonsViewController.isHomeDisabled());
+ });
}
/**
@@ -735,7 +809,7 @@
}
}
mWindowLayoutParams.height = height;
- mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
+ mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
@@ -783,6 +857,21 @@
}
/**
+ * Applies forcibly show flag to taskbar window iff transient taskbar is unstashed.
+ */
+ public void applyForciblyShownFlagWhileTransientTaskbarUnstashed(boolean shouldForceShow) {
+ if (!DisplayController.isTransientTaskbar(this)) {
+ return;
+ }
+ if (shouldForceShow) {
+ mWindowLayoutParams.forciblyShownTypes |= WindowInsets.Type.navigationBars();
+ } else {
+ mWindowLayoutParams.forciblyShownTypes &= ~WindowInsets.Type.navigationBars();
+ }
+ notifyUpdateLayoutParams();
+ }
+
+ /**
* Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
* window. If we're now focusable, also move nav buttons to a separate window above IME.
*/
@@ -888,8 +977,8 @@
}
} catch (NullPointerException
- | ActivityNotFoundException
- | SecurityException e) {
+ | ActivityNotFoundException
+ | SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
.show();
Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e);
@@ -936,9 +1025,10 @@
if (recents == null) {
return;
}
- recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
- info.getComponentKey(),
- foundTask -> {
+ recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
+ Collections.singletonList(info.getComponentKey()),
+ foundTasks -> {
+ @Nullable Task foundTask = foundTasks.get(0);
if (foundTask != null) {
TaskView foundTaskView =
recents.getTaskViewByTaskId(foundTask.key.id);
@@ -982,6 +1072,7 @@
/**
* Called when we detect a long press in the nav region before passing the gesture slop.
+ *
* @return Whether taskbar handled the long press, and thus should cancel the gesture.
*/
public boolean onLongPressToUnstashTaskbar() {
@@ -992,10 +1083,19 @@
* Called when we want to unstash taskbar when user performs swipes up gesture.
*/
public void onSwipeToUnstashTaskbar() {
- mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
+ mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false);
mControllers.taskbarEduTooltipController.hide();
}
+ /**
+ * Called when we want to open bubblebar when user performs swipes up gesture.
+ */
+ public void onSwipeToOpenBubblebar() {
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ controllers.bubbleStashController.showBubbleBar(/* expandBubbles= */ true);
+ });
+ }
+
/** Returns {@code true} if Taskbar All Apps is open. */
public boolean isTaskbarAllAppsOpen() {
return mControllers.taskbarAllAppsController.isOpen();
@@ -1041,7 +1141,7 @@
* Called when we detect a motion down or up/cancel in the nav region while stashed.
*
* @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
- * @param forceUnstash Whether we force the unstash hint.
+ * @param forceUnstash Whether we force the unstash hint.
*/
public void startTaskbarUnstashHint(boolean animateForward, boolean forceUnstash) {
// TODO(b/270395798): Clean up forceUnstash after removing long-press unstashing code.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
index 70999e7..8ab2ffa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
@@ -45,6 +45,8 @@
public static final int FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER = 1 << 4;
// Transient Taskbar is temporarily unstashed (pending a timeout).
public static final int FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR = 1 << 5;
+ // User has hovered the taskbar.
+ public static final int FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS = 1 << 6;
@IntDef(flag = true, value = {
FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
@@ -53,6 +55,7 @@
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER,
FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
+ FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutohideSuspendFlag {}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index ca2d1f8..d237c1f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -22,12 +22,12 @@
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
+import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapRange
import com.android.launcher3.Utilities.mapToRange
-import com.android.launcher3.anim.Interpolators
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.util.DisplayController
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index d3f80e3..d82f501 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -239,6 +239,7 @@
taskbarPopupController.onDestroy();
taskbarForceVisibleImmersiveController.onDestroy();
taskbarOverlayController.onDestroy();
+ taskbarAllAppsController.onDestroy();
navButtonController.onDestroy();
taskbarInsetsController.onDestroy();
voiceInteractionWindowController.onDestroy();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index a347908..b0d2c3c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -17,7 +17,6 @@
import android.annotation.SuppressLint
import android.content.Context
-import android.content.Intent
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
@@ -44,9 +43,6 @@
companion object {
private const val TAG = "TaskbarDividerPopupView"
private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
- private const val SETTINGS_PACKAGE_NAME = "com.android.settings"
- private const val CHANGE_NAVIGATION_MODE_ACTION =
- "com.android.settings.NAVIGATION_MODE_SETTINGS"
@JvmStatic
fun createAndPopulate(
@@ -103,21 +99,12 @@
super.onFinishInflate()
val taskbarSwitchOption = findViewById<LinearLayout>(R.id.taskbar_switch_option)
val alwaysShowTaskbarSwitch = findViewById<Switch>(R.id.taskbar_pinning_switch)
- val navigationModeChangeOption =
- findViewById<LinearLayout>(R.id.navigation_mode_switch_option)
alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
taskbarSwitchOption.setOnClickListener {
alwaysShowTaskbarSwitch.isClickable = true
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
onClickAlwaysShowTaskbarSwitchOption()
}
- navigationModeChangeOption.setOnClickListener {
- context.startActivity(
- Intent(CHANGE_NAVIGATION_MODE_ACTION)
- .setPackage(SETTINGS_PACKAGE_NAME)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- )
- }
}
/** Orient object as usual and then center object horizontally. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 040b8f7..3c7196a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -15,16 +15,18 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
@@ -47,6 +49,7 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.internal.logging.InstanceId;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
@@ -55,7 +58,6 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragDriver;
import com.android.launcher3.dragndrop.DragOptions;
@@ -73,6 +75,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.views.BubbleTextHolder;
import com.android.quickstep.util.LogUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.systemui.shared.recents.model.Task;
@@ -149,6 +152,9 @@
View view,
@Nullable DragPreviewProvider dragPreviewProvider,
@Nullable Point iconShift) {
+ if (view instanceof BubbleTextHolder) {
+ view = ((BubbleTextHolder) view).getBubbleText();
+ }
if (!(view instanceof BubbleTextView) || mDisallowLongClick) {
return false;
}
@@ -193,13 +199,21 @@
dragLayerY += dragRect.top;
DragOptions dragOptions = new DragOptions();
- dragOptions.preDragCondition = null;
- PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
- mControllers.taskbarPopupController.showForIcon(btv);
- if (popupContainer != null) {
- dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
- }
+ // First, see if view is a search result that needs custom pre-drag conditions.
+ dragOptions.preDragCondition =
+ mControllers.taskbarAllAppsController.createPreDragConditionForSearch(btv);
+
if (dragOptions.preDragCondition == null) {
+ // See if view supports a popup container.
+ PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
+ mControllers.taskbarPopupController.showForIcon(btv);
+ if (popupContainer != null) {
+ dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
+ }
+ }
+
+ if (dragOptions.preDragCondition == null) {
+ // Fallback pre-drag condition.
dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
private DragView mDragView;
@@ -213,13 +227,8 @@
mDragView = dragObject.dragView;
if (!shouldStartDrag(0)) {
- mDragView.setOnAnimationEndCallback(() -> {
- // Drag might be cancelled during the DragView animation, so check
- // mIsPreDrag again.
- if (mIsInPreDrag) {
- callOnDragStart();
- }
- });
+ mDragView.setOnScaleAnimEndCallback(
+ TaskbarDragController.this::onPreDragAnimationEnd);
}
}
@@ -230,12 +239,13 @@
};
}
+ Point dragOffset = dragOptions.preDragCondition.getDragOffset();
return startDrag(
drawable,
/* view = */ null,
/* originalView = */ btv,
- dragLayerX,
- dragLayerY,
+ dragLayerX + dragOffset.x,
+ dragLayerY + dragOffset.y,
(View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
(ItemInfo) btv.getTag(),
dragRect,
@@ -290,6 +300,11 @@
mDragObject.dragInfo = dragInfo;
mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
+ if (mOptions.preDragCondition != null) {
+ dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0
+ || mOptions.preDragCondition.getDragOffset().y != 0);
+ }
+
if (dragRegion != null) {
dragView.setDragRegion(new Rect(dragRegion));
}
@@ -308,6 +323,14 @@
return dragView;
}
+ /** Invoked when an animation running as part of pre-drag finishes. */
+ public void onPreDragAnimationEnd() {
+ // Drag might be cancelled during the DragView animation, so check mIsPreDrag again.
+ if (mIsInPreDrag) {
+ callOnDragStart();
+ }
+ }
+
@Override
protected void callOnDragStart() {
super.callOnDragStart();
@@ -383,6 +406,17 @@
item.user));
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId);
+ } else if (item.itemType == ITEM_TYPE_SEARCH_ACTION) {
+ // TODO(b/289261756): Buggy behavior when split opposite to an existing search pane.
+ intent.putExtra(
+ ClipDescription.EXTRA_PENDING_INTENT,
+ PendingIntent.getActivityAsUser(
+ mActivity,
+ /* requestCode= */ 0,
+ item.getIntent(),
+ PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+ /* options= */ null,
+ item.user));
} else {
intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
launcherApps.getMainActivityLaunchIntent(item.getIntent().getComponent(),
@@ -642,7 +676,7 @@
final FloatProp mScale = new FloatProp(1f, toScale, 0,
ANIM_DURATION_RETURN_ICON_TO_TASKBAR, FAST_OUT_SLOW_IN);
final FloatProp mAlpha = new FloatProp(1f, toAlpha, 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCEL_2);
+ ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCELERATE_2);
@Override
public void onUpdate(float percent, boolean initOnly) {
animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index f6de926..e521154 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.RectF;
+import android.media.permission.SafeCloseable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.KeyEvent;
@@ -31,6 +32,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -68,6 +70,7 @@
// Initialized in init.
private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
+ private SafeCloseable mViewCaptureCloseable;
private float mTaskbarBackgroundOffset;
@@ -128,12 +131,14 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
+ mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
+ .startCapture(getRootView(), ".Taskbar");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
-
+ mViewCaptureCloseable.close();
onDestroy(true);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
new file mode 100644
index 0000000..c3ec1e5
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -0,0 +1,177 @@
+/*
+ * 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.taskbar;
+
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.View.ALPHA;
+import static android.view.View.SCALE_Y;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;
+
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_EXCEPT_ON_BOARD_POPUP;
+import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
+import static com.android.launcher3.views.ArrowTipView.TEXT_ALPHA;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.ContextThemeWrapper;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.app.animation.Interpolators;
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.views.ArrowTipView;
+
+/**
+ * Controls showing a tooltip in the taskbar above each icon when it is hovered.
+ */
+public class TaskbarHoverToolTipController implements View.OnHoverListener {
+
+ @VisibleForTesting protected static final int HOVER_TOOL_TIP_REVEAL_START_DELAY = 400;
+ private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 300;
+ private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150;
+
+ private final Handler mHoverToolTipHandler = new Handler(Looper.getMainLooper());
+ private final Runnable mRevealHoverToolTipRunnable = this::revealHoverToolTip;
+ private final Runnable mHideHoverToolTipRunnable = this::hideHoverToolTip;
+
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarView mTaskbarView;
+ private final View mHoverView;
+ private final ArrowTipView mHoverToolTipView;
+ private final String mToolTipText;
+
+ public TaskbarHoverToolTipController(TaskbarActivityContext activity, TaskbarView taskbarView,
+ View hoverView) {
+ mActivity = activity;
+ mTaskbarView = taskbarView;
+ mHoverView = hoverView;
+
+ if (mHoverView instanceof BubbleTextView) {
+ mToolTipText = ((BubbleTextView) mHoverView).getText().toString();
+ } else if (mHoverView instanceof FolderIcon
+ && ((FolderIcon) mHoverView).mInfo.title != null) {
+ mToolTipText = ((FolderIcon) mHoverView).mInfo.title.toString();
+ } else {
+ mToolTipText = null;
+ }
+
+ ContextThemeWrapper arrowContextWrapper = new ContextThemeWrapper(mActivity,
+ R.style.ArrowTipTaskbarStyle);
+ mHoverToolTipView = new ArrowTipView(arrowContextWrapper, /* isPointingUp = */ false,
+ R.layout.arrow_toast);
+
+ AnimatorSet hoverCloseAnimator = new AnimatorSet();
+ ObjectAnimator textCloseAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0);
+ textCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0, 0.33f));
+ ObjectAnimator alphaCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0);
+ alphaCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 0.66f));
+ ObjectAnimator scaleCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0);
+ scaleCloseAnimator.setInterpolator(Interpolators.STANDARD);
+ hoverCloseAnimator.playTogether(
+ textCloseAnimator,
+ alphaCloseAnimator,
+ scaleCloseAnimator);
+ hoverCloseAnimator.setStartDelay(0);
+ hoverCloseAnimator.setDuration(HOVER_TOOL_TIP_EXIT_DURATION);
+ mHoverToolTipView.setCustomCloseAnimation(hoverCloseAnimator);
+
+ AnimatorSet hoverOpenAnimator = new AnimatorSet();
+ ObjectAnimator textOpenAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 255);
+ textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 1f));
+ ObjectAnimator scaleOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 1f);
+ scaleOpenAnimator.setInterpolator(Interpolators.EMPHASIZED);
+ ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 1f);
+ alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.1f, 0.33f));
+ hoverOpenAnimator.playTogether(
+ scaleOpenAnimator,
+ textOpenAnimator,
+ alphaOpenAnimator);
+ hoverOpenAnimator.setStartDelay(HOVER_TOOL_TIP_REVEAL_START_DELAY);
+ hoverOpenAnimator.setDuration(HOVER_TOOL_TIP_REVEAL_DURATION);
+ mHoverToolTipView.setCustomOpenAnimation(hoverOpenAnimator);
+
+ mHoverToolTipView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mHoverToolTipView.setPivotY(bottom);
+ mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top));
+ });
+ mHoverToolTipView.setScaleY(0f);
+ mHoverToolTipView.setAlpha(0f);
+ }
+
+ @Override
+ public boolean onHover(View v, MotionEvent event) {
+ boolean isAnyOtherFloatingViewOpen =
+ AbstractFloatingView.hasOpenView(mActivity, TYPE_ALL_EXCEPT_ON_BOARD_POPUP);
+ if (isAnyOtherFloatingViewOpen) {
+ mHoverToolTipHandler.removeCallbacksAndMessages(null);
+ }
+ // If hover leaves a taskbar icon animate the tooltip closed.
+ if (event.getAction() == ACTION_HOVER_EXIT) {
+ startHideHoverToolTip();
+ mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, false);
+ return true;
+ } else if (!isAnyOtherFloatingViewOpen && event.getAction() == ACTION_HOVER_ENTER) {
+ // If hovering above a taskbar icon starts, animate the tooltip open. Do not
+ // reveal if any floating views such as folders or edu pop-ups are open.
+ startRevealHoverToolTip();
+ mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
+ return true;
+ }
+ return false;
+ }
+
+ private void startRevealHoverToolTip() {
+ mHoverToolTipHandler.postDelayed(mRevealHoverToolTipRunnable,
+ HOVER_TOOL_TIP_REVEAL_START_DELAY);
+ }
+
+ private void revealHoverToolTip() {
+ if (mHoverView == null || mToolTipText == null) {
+ return;
+ }
+ if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
+ return;
+ }
+ mActivity.setTaskbarWindowFullscreen(true);
+ Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
+ mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
+ mTaskbarView.getTop(), /* shouldAutoClose= */ false);
+ }
+
+ private void startHideHoverToolTip() {
+ mHoverToolTipHandler.removeCallbacks(mRevealHoverToolTipRunnable);
+ int accessibilityHideTimeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(
+ mActivity, /* originalTimeout= */ 0, FLAG_CONTENT_TEXT);
+ mHoverToolTipHandler.postDelayed(mHideHoverToolTipRunnable, accessibilityHideTimeout);
+ }
+
+ private void hideHoverToolTip() {
+ mHoverToolTipView.close(/* animate = */ true);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index d6e559a..5a4534e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -15,12 +15,15 @@
*/
package com.android.launcher3.taskbar
+import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
import android.graphics.Insets
import android.graphics.Region
import android.os.Binder
import android.os.IBinder
+import android.view.Gravity
import android.view.InsetsFrameProvider
import android.view.InsetsFrameProvider.SOURCE_DISPLAY
+import android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER
import android.view.InsetsSource.FLAG_SUPPRESS_SCRIM
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME
@@ -41,6 +44,7 @@
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.DisplayController
import java.io.PrintWriter
+import kotlin.jvm.optionals.getOrNull
/** Handles the insets that Taskbar provides to underlying apps and the IME. */
class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTaskbarController {
@@ -55,13 +59,13 @@
private val touchableRegion: Region = Region()
private val insetsOwner: IBinder = Binder()
private val deviceProfileChangeListener = { _: DeviceProfile ->
- onTaskbarWindowHeightOrInsetsChanged()
+ onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
}
private val gestureNavSettingsObserver =
GestureNavigationSettingsObserver(
context.mainThreadHandler,
context,
- this::onTaskbarWindowHeightOrInsetsChanged
+ this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged
)
// Initialized in init.
@@ -71,7 +75,7 @@
fun init(controllers: TaskbarControllers) {
this.controllers = controllers
windowLayoutParams = context.windowLayoutParams
- onTaskbarWindowHeightOrInsetsChanged()
+ onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
context.addOnDeviceProfileChangeListener(deviceProfileChangeListener)
gestureNavSettingsObserver.registerForCallingUser()
@@ -82,109 +86,165 @@
gestureNavSettingsObserver.unregister()
}
- fun onTaskbarWindowHeightOrInsetsChanged() {
- if (context.isGestureNav) {
- windowLayoutParams.providedInsets =
- arrayOf(
- InsetsFrameProvider(insetsOwner, 0, navigationBars())
- .setFlags(FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM),
- InsetsFrameProvider(insetsOwner, 0, tappableElement()),
- InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
- InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
- .setSource(SOURCE_DISPLAY),
- InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
- .setSource(SOURCE_DISPLAY)
- )
- } else {
- windowLayoutParams.providedInsets =
- arrayOf(
- InsetsFrameProvider(insetsOwner, 0, navigationBars()),
- InsetsFrameProvider(insetsOwner, 0, tappableElement()),
- InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures())
- )
- }
-
- val touchableHeight = controllers.taskbarStashController.touchableHeight
- touchableRegion.set(
- 0,
- windowLayoutParams.height - touchableHeight,
- context.deviceProfile.widthPx,
- windowLayoutParams.height
- )
- val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
+ fun onTaskbarOrBubblebarWindowHeightOrInsetsChanged() {
val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
- val res = context.resources
- for (provider in windowLayoutParams.providedInsets) {
- if (provider.type == navigationBars() || provider.type == mandatorySystemGestures()) {
- provider.insetsSize = getInsetsByNavMode(contentHeight)
- } else if (provider.type == tappableElement()) {
- provider.insetsSize = getInsetsByNavMode(tappableHeight)
- } else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
- provider.insetsSize =
- Insets.of(
- gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res),
- 0,
- 0,
- 0
- )
- } else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
- provider.insetsSize =
- Insets.of(
- 0,
- 0,
- gestureNavSettingsObserver.getRightSensitivityForCallingUser(res),
- 0
- )
- }
- }
-
- val imeInsetsSize = getInsetsByNavMode(taskbarHeightForIme)
- val insetsSizeOverride =
- arrayOf(
- InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
- )
- // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
- val visInsetsSizeForGestureNavTappableElement = getInsetsByNavMode(0)
- val insetsSizeOverrideForGestureNavTappableElement =
- arrayOf(
- InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
- InsetsFrameProvider.InsetsSizeOverride(
- TYPE_VOICE_INTERACTION,
- visInsetsSizeForGestureNavTappableElement
- ),
- )
- for (provider in windowLayoutParams.providedInsets) {
- if (context.isGestureNav && provider.type == tappableElement()) {
- provider.insetsSizeOverrides = insetsSizeOverrideForGestureNavTappableElement
- } else if (provider.type != systemGestures()) {
- // We only override insets at the bottom of the screen
- provider.insetsSizeOverrides = insetsSizeOverride
- }
- }
-
// We only report tappableElement height for unstashed, persistent taskbar,
// which is also when we draw the rounded corners above taskbar.
- windowLayoutParams.insetsRoundedCornerFrame = tappableHeight > 0
+ val insetsRoundedCornerFlag =
+ if (tappableHeight > 0) {
+ FLAG_INSETS_ROUNDED_CORNER
+ } else {
+ 0
+ }
+
+ windowLayoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
+ if (!context.isGestureNav) {
+ if (windowLayoutParams.paramsForRotation != null) {
+ for (layoutParams in windowLayoutParams.paramsForRotation) {
+ layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
+ }
+ }
+ }
+
+ val taskbarTouchableHeight = controllers.taskbarStashController.touchableHeight
+ val bubblesTouchableHeight =
+ if (controllers.bubbleControllers.isPresent) {
+ controllers.bubbleControllers.get().bubbleStashController.touchableHeight
+ } else {
+ 0
+ }
+ val touchableHeight = Math.max(taskbarTouchableHeight, bubblesTouchableHeight)
+
+ if (
+ controllers.bubbleControllers.isPresent &&
+ controllers.bubbleControllers.get().bubbleStashController.isBubblesShowingOnHome
+ ) {
+ val iconBounds =
+ controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
+ touchableRegion.set(
+ iconBounds.left,
+ iconBounds.top,
+ iconBounds.right,
+ iconBounds.bottom
+ )
+ } else {
+ touchableRegion.set(
+ 0,
+ windowLayoutParams.height - touchableHeight,
+ context.deviceProfile.widthPx,
+ windowLayoutParams.height
+ )
+ }
+
+ val gravity = windowLayoutParams.gravity
+ for (provider in windowLayoutParams.providedInsets) {
+ setProviderInsets(provider, gravity)
+ }
+
+ if (windowLayoutParams.paramsForRotation != null) {
+ // Add insets for navbar rotated params
+ for (layoutParams in windowLayoutParams.paramsForRotation) {
+ for (provider in layoutParams.providedInsets) {
+ setProviderInsets(provider, layoutParams.gravity)
+ }
+ }
+ }
context.notifyUpdateLayoutParams()
}
/**
- * @return [Insets] where the [bottomInset] is either used as a bottom inset or
- *
- * ```
- * right/left inset if using 3 button nav
- * ```
+ * The inset types and number of insets provided have to match for both gesture nav and button
+ * nav. The values and the order of the elements in array are allowed to differ.
+ * Reason being WM does not allow types and number of insets changing for a given window once it
+ * is added into the hierarchy for performance reasons.
*/
- private fun getInsetsByNavMode(bottomInset: Int): Insets {
- val devicePortrait = !context.deviceProfile.isLandscape
- if (!TaskbarManager.isPhoneButtonNavMode(context) || devicePortrait) {
+ private fun getProvidedInsets(insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
+ val navBarsFlag =
+ (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
+ return arrayOf(
+ InsetsFrameProvider(insetsOwner, 0, navigationBars())
+ .setFlags(
+ navBarsFlag,
+ FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
+ ),
+ InsetsFrameProvider(insetsOwner, 0, tappableElement()),
+ InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
+ InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
+ .setSource(SOURCE_DISPLAY),
+ InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
+ .setSource(SOURCE_DISPLAY)
+ )
+ }
+
+ private fun setProviderInsets(provider: InsetsFrameProvider, gravity: Int) {
+ val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
+ val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
+ val res = context.resources
+ if (provider.type == navigationBars() || provider.type == mandatorySystemGestures()) {
+ provider.insetsSize = getInsetsForGravity(contentHeight, gravity)
+ } else if (provider.type == tappableElement()) {
+ provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
+ } else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
+ val leftIndexInset =
+ if (context.isThreeButtonNav) 0
+ else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
+ provider.insetsSize = Insets.of(leftIndexInset, 0, 0, 0)
+ } else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
+ val rightIndexInset =
+ if (context.isThreeButtonNav) 0
+ else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
+ provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
+ }
+
+
+ // When in gesture nav, report the stashed height to the IME, to allow hiding the
+ // IME navigation bar.
+ val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
+ getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity);
+ } else {
+ getInsetsForGravity(taskbarHeightForIme, gravity)
+ }
+ val imeInsetsSizeOverride =
+ arrayOf(
+ InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+ )
+ // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
+ val visInsetsSizeForTappableElement =
+ if (context.isGestureNav) getInsetsForGravity(0, gravity)
+ else getInsetsForGravity(tappableHeight, gravity)
+ val insetsSizeOverrideForTappableElement =
+ arrayOf(
+ InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+ InsetsFrameProvider.InsetsSizeOverride(
+ TYPE_VOICE_INTERACTION,
+ visInsetsSizeForTappableElement
+ ),
+ )
+ if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+ && provider.type == tappableElement()) {
+ provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
+ } else if (provider.type != systemGestures()) {
+ // We only override insets at the bottom of the screen
+ provider.insetsSizeOverrides = imeInsetsSizeOverride
+ }
+ }
+
+ /**
+ * @return [Insets] where the [inset] is either used as a bottom inset or
+ * right/left inset if using 3 button nav
+ */
+ private fun getInsetsForGravity(inset: Int, gravity: Int): Insets {
+ if ((gravity and Gravity.BOTTOM) == Gravity.BOTTOM) {
// Taskbar or portrait phone mode
- return Insets.of(0, 0, 0, bottomInset)
+ return Insets.of(0, 0, 0, inset)
}
// TODO(b/230394142): seascape
- return Insets.of(0, 0, bottomInset, 0)
+ val isSeascape = (gravity and Gravity.START) == Gravity.START
+ val leftInset = if (isSeascape) inset else 0
+ val rightInset = if (isSeascape) 0 else inset
+ return Insets.of(leftInset , 0, rightInset, 0)
}
/**
@@ -199,6 +259,9 @@
context.dragLayer,
insetsInfo.touchableRegion
)
+ val bubbleBarVisible =
+ controllers.bubbleControllers.isPresent &&
+ controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
var insetsIsTouchableRegion = true
if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
// Let touches pass through us.
@@ -219,16 +282,33 @@
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME)
insetsIsTouchableRegion = false
} else if (
- controllers.taskbarViewController.areIconsVisible() || context.isNavBarKidsModeActive
+ controllers.taskbarViewController.areIconsVisible() ||
+ context.isNavBarKidsModeActive ||
+ bubbleBarVisible
) {
// Taskbar has some touchable elements, take over the full taskbar area
if (
controllers.uiController.isInOverview &&
DisplayController.isTransientTaskbar(context)
) {
- insetsInfo.touchableRegion.set(
+ val region =
controllers.taskbarActivityContext.dragLayer.lastDrawnTransientRect.toRegion()
- )
+ val bubbleBarBounds =
+ controllers.bubbleControllers.getOrNull()?.let { bubbleControllers ->
+ if (!bubbleControllers.bubbleStashController.isBubblesShowingOnOverview) {
+ return@let null
+ }
+ if (!bubbleControllers.bubbleBarViewController.isBubbleBarVisible) {
+ return@let null
+ }
+ bubbleControllers.bubbleBarViewController.bubbleBarBounds
+ }
+
+ // Include the bounds of the bubble bar in the touchable region if they exist.
+ if (bubbleBarBounds != null) {
+ region.op(bubbleBarBounds, Region.Op.UNION)
+ }
+ insetsInfo.touchableRegion.set(region)
} else {
insetsInfo.touchableRegion.set(touchableRegion)
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index ddea51f..90f7bea 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -204,7 +204,7 @@
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
// TODO(b/279514548) Cleans up bad state that can occur when user interacts with
// taskbar on top of transparent activity.
- if (finalState == LauncherState.NORMAL && mLauncher.isResumed()) {
+ if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) {
updateStateForFlag(FLAG_RESUMED, true);
}
applyState();
@@ -249,7 +249,6 @@
mIconAlignment.finishAnimation();
- Log.d("b/260135164", "onDestroy - updateIconAlphaForHome(1)");
mLauncher.getHotseat().setIconsAlpha(1f);
mLauncher.getStateManager().removeStateListener(mStateListener);
@@ -409,6 +408,14 @@
+ ", mLauncherState: " + mLauncherState
+ ", toAlignment: " + toAlignment);
}
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ // Show the bubble bar when on launcher home or in overview.
+ boolean onHome = isInLauncher && mLauncherState == LauncherState.NORMAL;
+ boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
+ controllers.bubbleStashController.setBubblesShowingOnHome(onHome);
+ controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
+ });
+
AnimatorSet animatorSet = new AnimatorSet();
if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
@@ -479,7 +486,8 @@
public void onAnimationEnd(Animator animation) {
TaskbarStashController stashController =
mControllers.taskbarStashController;
- stashController.updateAndAnimateTransientTaskbar(/* stash */ true);
+ stashController.updateAndAnimateTransientTaskbar(
+ /* stash */ true, /* bubblesShouldFollow */ true);
}
});
} else {
@@ -636,8 +644,6 @@
public void onAnimationEnd(Animator animation) {
if (isInStashedState && committed) {
// Reset hotseat alpha to default
- Log.d("b/260135164",
- "playStateTransitionAnim#onAnimationEnd - setIconsAlpha(1)");
mLauncher.getHotseat().setIconsAlpha(1);
}
}
@@ -705,8 +711,6 @@
private void updateIconAlphaForHome(float alpha) {
if (mControllers.taskbarActivityContext.isDestroyed()) {
- Log.e("b/260135164", "updateIconAlphaForHome is called after Taskbar is destroyed",
- new Exception());
return;
}
mIconAlphaForHome.setValue(alpha);
@@ -717,9 +721,6 @@
* Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
* should not be visible at the same time.
*/
- Log.d("b/260135164",
- "updateIconAlphaForHome - setIconsAlpha(" + (hotseatVisible ? 1 : 0)
- + "), isTaskbarPresent: " + mLauncher.getDeviceProfile().isTaskbarPresent);
mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0);
if (mIsQsbInline) {
mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 738ff87..b115ca8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -22,13 +22,13 @@
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
-import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
-import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentCallbacks;
import android.content.Context;
@@ -41,6 +41,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
+import android.os.Trace;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
@@ -50,19 +51,20 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
@@ -77,6 +79,22 @@
private static final String TAG = "TaskbarManager";
private static final boolean DEBUG = false;
+ /**
+ * All the configurations which do not initiate taskbar recreation.
+ * This includes all the configurations defined in Launcher's manifest entry and
+ * ActivityController#filterConfigChanges
+ */
+ private static final int SKIP_RECREATE_CONFIG_CHANGES = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
+ | ActivityInfo.CONFIG_KEYBOARD
+ | ActivityInfo.CONFIG_KEYBOARD_HIDDEN
+ | ActivityInfo.CONFIG_MCC
+ | ActivityInfo.CONFIG_MNC
+ | ActivityInfo.CONFIG_NAVIGATION
+ | ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+
public static final boolean FLAG_HIDE_NAVBAR_WINDOW =
SystemProperties.getBoolean("persist.wm.debug.hide_navbar_window", false);
@@ -87,12 +105,11 @@
Settings.Secure.NAV_BAR_KIDS_MODE);
private final Context mContext;
- private final DisplayController mDisplayController;
private final TaskbarNavButtonController mNavButtonController;
- private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
- private final SettingsCache.OnChangeListener mNavBarKidsModeListener;
private final ComponentCallbacks mComponentCallbacks;
- private final SimpleBroadcastReceiver mShutdownReceiver;
+
+ private final SimpleBroadcastReceiver mShutdownReceiver =
+ new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
// The source for this provider is set when Launcher is available
// We use 'non-destroyable' version here so the original provider won't be destroyed
@@ -100,7 +117,6 @@
// It's destruction/creation will be managed by the activity.
private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
new NonDestroyableScopedUnfoldTransitionProgressProvider();
- private NavigationMode mNavMode;
private TaskbarActivityContext mTaskbarActivityContext;
private StatefulActivity mActivity;
@@ -111,19 +127,11 @@
private final TaskbarSharedState mSharedState = new TaskbarSharedState();
/**
- * We use WindowManager's ComponentCallbacks() for most of the config changes, however for
- * navigation mode, that callback gets called too soon, before it's internal navigation mode
- * reflects the current one.
- * DisplayController's callback is delayed enough to get the correct nav mode value
- *
- * We also use density change here because DeviceProfile has had a chance to update it's state
- * whereas density for component callbacks registered in this class don't update DeviceProfile.
- * Confused? Me too. Make it less confusing (TODO: b/227669780)
- *
- * Flags used with {@link #mDispInfoChangeListener}
+ * We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity)
+ * which comes via a different channel
*/
- private static final int CHANGE_FLAGS = CHANGE_NAVIGATION_MODE | CHANGE_DENSITY;
- private final DisplayController.DisplayInfoChangeListener mDispInfoChangeListener;
+ private final OnIDPChangeListener mIdpChangeListener = c -> recreateTaskbar();
+ private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
private boolean mUserUnlocked = false;
@@ -144,101 +152,121 @@
}
};
+ private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
+ new ActivityLifecycleCallbacksAdapter() {
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ if (mActivity != activity) return;
+ if (mActivity != null) {
+ mActivity.removeOnDeviceProfileChangeListener(
+ mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "unregistering activity lifecycle callbacks from "
+ + "onActivityDestroyed.");
+ mActivity.unregisterActivityLifecycleCallbacks(this);
+ }
+ mActivity = null;
+ debugWhyTaskbarNotDestroyed("clearActivity");
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+ }
+ mUnfoldProgressProvider.setSourceProvider(null);
+ }
+ };
+
+ UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
+ new UnfoldTransitionProgressProvider.TransitionProgressListener() {
+ @Override
+ public void onTransitionStarted() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition started getting called.");
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition progress : " + progress);
+ }
+
+ @Override
+ public void onTransitionFinishing() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition finishing getting called.");
+
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition finished getting called.");
+
+ }
+ };
+
@SuppressLint("WrongConstant")
public TaskbarManager(TouchInteractionService service) {
- mDisplayController = DisplayController.INSTANCE.get(service);
Display display =
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
mNavButtonController = new TaskbarNavButtonController(service,
- SystemUiProxy.INSTANCE.get(mContext), new Handler());
- mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
- mNavBarKidsModeListener = isNavBarKidsMode -> recreateTaskbar();
- // TODO(b/227669780): Consolidate this w/ DisplayController callbacks
+ SystemUiProxy.INSTANCE.get(mContext), new Handler(),
+ AssistUtils.newInstance(mContext));
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
+ "onConfigurationChanged: " + newConfig);
debugWhyTaskbarNotDestroyed(
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
DeviceProfile dp = mUserUnlocked
? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
: null;
- int configDiff = mOldConfig.diff(newConfig);
- int configDiffForRecreate = configDiff;
- int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
- | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE
- | ActivityInfo.CONFIG_SCREEN_SIZE;
- if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
- && mTaskbarActivityContext != null && dp != null
- && !isPhoneMode(dp)) {
- // Additional check since this callback gets fired multiple times w/o
- // screen size changing, or when simply rotating the device.
- // In the case of phone device rotation, we do want to call recreateTaskbar()
- DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile();
- boolean isOrientationChange =
- (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0;
+ int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
- int newOrientation = newConfig.windowConfiguration.getRotation();
- int oldOrientation = mOldConfig.windowConfiguration.getRotation();
- int oldWidth = isOrientationChange ? oldDp.heightPx : oldDp.widthPx;
- int oldHeight = isOrientationChange ? oldDp.widthPx : oldDp.heightPx;
-
- if ((dp.widthPx == oldWidth && dp.heightPx == oldHeight)
- || (newOrientation == oldOrientation)) {
- configDiffForRecreate &= ~ActivityInfo.CONFIG_SCREEN_SIZE;
- }
- }
if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
// Only recreate for theme changes, not other UI mode changes such as docking.
int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
if (oldUiNightMode == newUiNightMode) {
- configDiffForRecreate &= ~ActivityInfo.CONFIG_UI_MODE;
+ configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
}
}
debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
- + "configDiffForRecreate="
- + Configuration.configurationDiffToString(configDiffForRecreate));
- if ((configDiffForRecreate & configsRequiringRecreate) != 0) {
+ + "configDiff=" + Configuration.configurationDiffToString(configDiff));
+ if (configDiff != 0 || mTaskbarActivityContext == null) {
recreateTaskbar();
} else {
// Config change might be handled without re-creating the taskbar
- if (mTaskbarActivityContext != null) {
- if (dp != null && !isTaskbarPresent(dp)) {
- destroyExistingTaskbar();
- } else {
- if (dp != null && isTaskbarPresent(dp)) {
- mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
+ if (dp != null && !isTaskbarPresent(dp)) {
+ destroyExistingTaskbar();
+ } else {
+ if (dp != null && isTaskbarPresent(dp)) {
+ if (FLAG_HIDE_NAVBAR_WINDOW) {
+ // Re-initialize for screen size change? Should this be done
+ // by looking at screen-size change flag in configDiff in the
+ // block above?
+ recreateTaskbar();
+ } else {
+ mTaskbarActivityContext.updateDeviceProfile(dp);
}
- mTaskbarActivityContext.onConfigurationChanged(configDiff);
}
+ mTaskbarActivityContext.onConfigurationChanged(configDiff);
}
}
- mOldConfig = newConfig;
+ mOldConfig = new Configuration(newConfig);
}
@Override
public void onLowMemory() { }
};
- mShutdownReceiver = new SimpleBroadcastReceiver(i ->
- destroyExistingTaskbar());
- mDispInfoChangeListener = (context, info, flags) -> {
- if ((flags & CHANGE_FLAGS) != 0) {
- mNavMode = info.navigationMode;
- recreateTaskbar();
- }
- debugWhyTaskbarNotDestroyed("DisplayInfoChangeListener#"
- + mDisplayController.getChangeFlagsString(flags));
- };
- mNavMode = mDisplayController.getInfo().navigationMode;
- mDisplayController.addChangeListener(mDispInfoChangeListener);
- SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
- mUserSetupCompleteListener);
- SettingsCache.INSTANCE.get(mContext).register(NAV_BAR_KIDS_MODE,
- mNavBarKidsModeListener);
+ SettingsCache.INSTANCE.get(mContext)
+ .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+ SettingsCache.INSTANCE.get(mContext)
+ .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
mContext.registerComponentCallbacks(mComponentCallbacks);
mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
@@ -279,6 +307,25 @@
}
/**
+ * Toggles All Apps for Taskbar or Launcher depending on the current state.
+ *
+ * @param homeAllAppsIntent Intent used if Taskbar is not enabled or Launcher is resumed.
+ */
+ public void toggleAllApps(Intent homeAllAppsIntent) {
+ if (mTaskbarActivityContext == null) {
+ mContext.startActivity(homeAllAppsIntent);
+ return;
+ }
+
+ if (mActivity != null && mActivity.isResumed() && !mActivity.isInState(OVERVIEW)) {
+ mContext.startActivity(homeAllAppsIntent);
+ return;
+ }
+
+ mTaskbarActivityContext.toggleAllApps();
+ }
+
+ /**
* Displays a frame of the first Launcher reveal animation.
*
* This should be used to run a first Launcher reveal animation whose progress matches a swipe
@@ -294,6 +341,7 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
+ LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
recreateTaskbar();
}
@@ -304,14 +352,18 @@
if (mActivity == activity) {
return;
}
- if (mActivity != null) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- }
+ removeActivityCallbacksAndListeners();
mActivity = activity;
debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity);
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "registering activity lifecycle callbacks from setActivity().");
+ mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(activity);
+ if (unfoldTransitionProgressProvider != null) {
+ unfoldTransitionProgressProvider.addCallback(mUnfoldTransitionProgressListener);
+ }
mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider);
if (mTaskbarActivityContext != null) {
@@ -349,59 +401,50 @@
}
/**
- * Clears a previously set {@link StatefulActivity}
- */
- public void clearActivity(@NonNull StatefulActivity activity) {
- if (mActivity == activity) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- mActivity = null;
- debugWhyTaskbarNotDestroyed("clearActivity");
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
- }
- mUnfoldProgressProvider.setSourceProvider(null);
- }
- }
-
- /**
* This method is called multiple times (ex. initial init, then when user unlocks) in which case
* we fully want to destroy an existing taskbar and create a new one.
* In other case (folding/unfolding) we don't need to remove and add window.
*/
@VisibleForTesting
public void recreateTaskbar() {
- DeviceProfile dp = mUserUnlocked ?
+ Trace.beginSection("recreateTaskbar");
+ try {
+ DeviceProfile dp = mUserUnlocked ?
LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
- destroyExistingTaskbar();
+ destroyExistingTaskbar();
- boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
- debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
+ boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
+ debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
+ " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
+ " FLAG_HIDE_NAVBAR_WINDOW=" + FLAG_HIDE_NAVBAR_WINDOW
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
- if (!isTaskbarEnabled) {
- SystemUiProxy.INSTANCE.get(mContext)
+ if (!isTaskbarEnabled) {
+ SystemUiProxy.INSTANCE.get(mContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
- return;
- }
+ return;
+ }
- if (mTaskbarActivityContext == null) {
- mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
+ if (mTaskbarActivityContext == null) {
+ mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp,
+ mNavButtonController,
mUnfoldProgressProvider);
- } else {
- mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
- }
- mTaskbarActivityContext.init(mSharedState);
+ } else {
+ mTaskbarActivityContext.updateDeviceProfile(dp);
+ }
+ mTaskbarActivityContext.init(mSharedState);
- if (mActivity != null) {
- mTaskbarActivityContext.setUIController(
+ if (mActivity != null) {
+ mTaskbarActivityContext.setUIController(
createTaskbarUIControllerForActivity(mActivity));
- }
+ }
- // We to wait until user unlocks the device to attach listener.
- LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
+ // We to wait until user unlocks the device to attach listener.
+ LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
TASKBAR_PINNING);
+ } finally {
+ Trace.endSection();
+ }
}
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
@@ -481,23 +524,38 @@
}
}
+ private void removeActivityCallbacksAndListeners() {
+ if (mActivity != null) {
+ mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "unregistering activity lifecycle callbacks from "
+ + "removeActivityCallbackAndListeners().");
+ mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+ UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
+ getUnfoldTransitionProgressProviderForActivity(mActivity);
+ if (unfoldTransitionProgressProvider != null) {
+ unfoldTransitionProgressProvider.removeCallback(mUnfoldTransitionProgressListener);
+ }
+ }
+ }
+
/**
* Called when the manager is no longer needed
*/
public void destroy() {
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
- if (mActivity != null) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- }
-
+ removeActivityCallbacksAndListeners();
UI_HELPER_EXECUTOR.execute(
() -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
destroyExistingTaskbar();
- mDisplayController.removeChangeListener(mDispInfoChangeListener);
- SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
- mUserSetupCompleteListener);
- SettingsCache.INSTANCE.get(mContext).unregister(NAV_BAR_KIDS_MODE,
- mNavBarKidsModeListener);
+ if (mUserUnlocked) {
+ LauncherAppState.getIDP(mContext).removeOnChangeListener(mIdpChangeListener);
+ }
+ SettingsCache.INSTANCE.get(mContext)
+ .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+ SettingsCache.INSTANCE.get(mContext)
+ .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mContext.unregisterReceiver(mShutdownReceiver);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6cf63a9..7692760 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -57,7 +57,7 @@
private final TaskbarView mContainer;
// Initialized in init.
- private TaskbarControllers mControllers;
+ protected TaskbarControllers mControllers;
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacksFactory.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacksFactory.kt
new file mode 100644
index 0000000..eb03b4a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacksFactory.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.taskbar
+
+import android.content.Context
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceBasedOverride
+import com.android.launcher3.util.ResourceBasedOverride.Overrides
+
+/** Creates [TaskbarModelCallbacks] instances. */
+open class TaskbarModelCallbacksFactory : ResourceBasedOverride {
+
+ open fun create(
+ activityContext: TaskbarActivityContext,
+ container: TaskbarView,
+ ): TaskbarModelCallbacks = TaskbarModelCallbacks(activityContext, container)
+
+ companion object {
+ @JvmStatic
+ fun newInstance(context: Context): TaskbarModelCallbacksFactory {
+ return Overrides.getObject(
+ TaskbarModelCallbacksFactory::class.java,
+ context,
+ R.string.taskbar_model_callbacks_factory_class,
+ )
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 610efeb..03df036 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -43,12 +43,16 @@
import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.AssistUtils;
+import com.android.quickstep.views.DesktopTaskView;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -105,15 +109,17 @@
private final TouchInteractionService mService;
private final SystemUiProxy mSystemUiProxy;
private final Handler mHandler;
+ private final AssistUtils mAssistUtils;
@Nullable private StatsLogManager mStatsLogManager;
private final Runnable mResetLongPress = this::resetScreenUnpin;
public TaskbarNavButtonController(TouchInteractionService service,
- SystemUiProxy systemUiProxy, Handler handler) {
+ SystemUiProxy systemUiProxy, Handler handler, AssistUtils assistUtils) {
mService = service;
mSystemUiProxy = systemUiProxy;
mHandler = handler;
+ mAssistUtils = assistUtils;
}
public void onButtonClick(@TaskbarButton int buttonType, View view) {
@@ -155,7 +161,7 @@
switch (buttonType) {
case BUTTON_HOME:
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
- startAssistant();
+ onLongPressHome();
return true;
case BUTTON_A11Y:
logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS);
@@ -267,6 +273,15 @@
private void navigateHome() {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController.onHomeActionTriggered();
+ }
+ }
+
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
}
@@ -295,13 +310,16 @@
}
}
- private void startAssistant() {
+ private void onLongPressHome() {
if (mScreenPinned || !mAssistantLongPressEnabled) {
return;
}
- Bundle args = new Bundle();
- args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
- mSystemUiProxy.startAssistant(args);
+ // Attempt to start Assist with AssistUtils, otherwise fall back to SysUi's implementation.
+ if (!mAssistUtils.tryStartAssistOverride(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
+ Bundle args = new Bundle();
+ args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
+ mSystemUiProxy.startAssistant(args);
+ }
}
private void showQuickSettings() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 5ea00cf..1c250bf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
@@ -23,6 +24,7 @@
import android.view.animation.PathInterpolator;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.util.DisplayController;
import com.android.quickstep.SystemUiProxy;
import java.io.PrintWriter;
@@ -63,6 +65,10 @@
* Updates the scrim state based on the flags.
*/
public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
+ if (BUBBLE_BAR_ENABLED && DisplayController.isTransientTaskbar(mActivity)) {
+ // These scrims aren't used if bubble bar & transient taskbar are active.
+ return;
+ }
final boolean bubblesExpanded = (stateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
final boolean manageMenuExpanded =
(stateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index daf9c47..a920dfa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -18,11 +18,11 @@
import static android.view.HapticFeedbackConstants.LONG_PRESS;
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
@@ -257,14 +257,15 @@
private boolean mEnableBlockingTimeoutDuringTests = false;
// Evaluate whether the handle should be stashed
+ private final IntPredicate mIsStashedPredicate = flags -> {
+ boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
+ boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
+ boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
+ boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
+ return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
+ };
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
- flags -> {
- boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
- boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
- boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
- boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
- return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
- });
+ mIsStashedPredicate);
private boolean mIsTaskbarSystemActionRegistered = false;
private TaskbarSharedState mTaskbarSharedState;
@@ -337,7 +338,6 @@
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
updateStateForFlag(FLAG_IN_APP, true);
applyState(/* duration = */ 0);
-
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
}
@@ -504,9 +504,19 @@
}
/**
- * Stash or unstashes the transient taskbar.
+ * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
+ * If bubble bar exists, it will match taskbars stashing behavior.
*/
public void updateAndAnimateTransientTaskbar(boolean stash) {
+ updateAndAnimateTransientTaskbar(stash, /* shouldBubblesFollow= */ true);
+ }
+
+ /**
+ * Stash or unstashes the transient taskbar.
+ * @param stash whether transient taskbar should be stashed.
+ * @param shouldBubblesFollow whether bubbles should match taskbars behavior.
+ */
+ public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
if (!DisplayController.isTransientTaskbar(mActivity)) {
return;
}
@@ -525,6 +535,34 @@
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
applyState();
}
+
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ if (shouldBubblesFollow) {
+ final boolean willStash = mIsStashedPredicate.test(mState);
+ if (willStash != controllers.bubbleStashController.isStashed()) {
+ // Typically bubbles gets stashed / unstashed along with Taskbar, however, if
+ // taskbar is becoming stashed because bubbles is being expanded, we don't want
+ // to stash bubbles.
+ if (willStash) {
+ controllers.bubbleStashController.stashBubbleBar();
+ } else {
+ controllers.bubbleStashController.showBubbleBar(false /* expandBubbles */);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Stashes transient taskbar after it has timed out.
+ */
+ private void updateAndAnimateTransientTaskbarForTimeout() {
+ // If bubbles are expanded we shouldn't stash them when taskbar is hidden
+ // for the timeout.
+ boolean bubbleBarExpanded = mControllers.bubbleControllers.isPresent()
+ && mControllers.bubbleControllers.get().bubbleBarViewController.isExpanded();
+ updateAndAnimateTransientTaskbar(/* stash= */ true,
+ /* shouldBubblesFollow= */ !bubbleBarExpanded);
}
/**
@@ -636,7 +674,10 @@
.setDuration(duration));
mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
- mAnimator.addListener(AnimatorListeners.forEndCallback(() -> mAnimator = null));
+ mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
+ mAnimator = null;
+ mIsStashed = isStashed;
+ }));
return;
}
@@ -830,8 +871,11 @@
.setDuration(isStashed ? duration / 2 : duration));
}
- private static void play(AnimatorSet as, Animator a, long startDelay, long duration,
+ private static void play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration,
Interpolator interpolator) {
+ if (a == null) {
+ return;
+ }
a.setDuration(duration);
a.setStartDelay(startDelay);
a.setInterpolator(interpolator);
@@ -897,7 +941,7 @@
private void onIsStashedChanged(boolean isStashed) {
mControllers.runAfterInit(() -> {
mControllers.stashedHandleViewController.onIsStashedChanged(isStashed);
- mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
+ mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
});
}
@@ -1053,6 +1097,7 @@
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
}
+ mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
}
private void notifyStashChange(boolean visible, boolean stashed) {
@@ -1147,7 +1192,7 @@
if (mControllers.taskbarAutohideSuspendController.isTransientTaskbarStashingSuspended()) {
return;
}
- updateAndAnimateTransientTaskbar(true);
+ updateAndAnimateTransientTaskbarForTimeout();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
index 1cc6672..deaf024 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
@@ -16,9 +16,9 @@
package com.android.launcher3.taskbar
import android.view.MotionEvent
+import com.android.app.animation.Interpolators.LINEAR
import com.android.launcher3.R
import com.android.launcher3.Utilities
-import com.android.launcher3.anim.Interpolators.LINEAR
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.touch.SingleAxisSwipeDetector
import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
@@ -108,7 +108,17 @@
}
override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
- if (!enabled || controllers.taskbarStashController.isStashed) {
+ if (!enabled) {
+ return false
+ }
+ val bubbleControllers = controllers.bubbleControllers.orElse(null)
+ if (bubbleControllers != null && bubbleControllers.bubbleBarViewController.isExpanded) {
+ return false
+ }
+ if (
+ (bubbleControllers == null || bubbleControllers.bubbleStashController.isStashed) &&
+ controllers.taskbarStashController.isStashed
+ ) {
return false
}
@@ -122,7 +132,12 @@
return true
}
} else if (ev.action == MotionEvent.ACTION_DOWN) {
- if (screenCoordinatesEv.y < gestureHeightYThreshold) {
+ val isDownOnBubbleBar =
+ (bubbleControllers != null &&
+ bubbleControllers.bubbleBarViewController.isEventOverAnyItem(
+ screenCoordinatesEv
+ ))
+ if (!isDownOnBubbleBar && screenCoordinatesEv.y < gestureHeightYThreshold) {
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarThresholdUtils.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarThresholdUtils.java
new file mode 100644
index 0000000..5b6fbef
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarThresholdUtils.java
@@ -0,0 +1,87 @@
+/*
+ * 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.taskbar;
+
+import static com.android.launcher3.Utilities.dpToPx;
+import static com.android.launcher3.Utilities.dpiFromPx;
+
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+
+import androidx.core.content.res.ResourcesCompat;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Utility class that contains the different taskbar thresholds logic.
+ */
+public class TaskbarThresholdUtils {
+
+ // We divide the screen into this many parts, and use the result to scale the thresholds to
+ // any size device. Note that this value was calculated arbitrarily by using two tablet devices
+ // as data points.
+ private static final float SCREEN_UNITS = 1 / 80f;
+
+ private static int getThreshold(Resources r, DeviceProfile dp, int thresholdDimen,
+ int multiplierDimen) {
+ if (!FeatureFlags.ENABLE_DYNAMIC_TASKBAR_THRESHOLDS.get()) {
+ return r.getDimensionPixelSize(thresholdDimen);
+ }
+
+ float landscapeScreenHeight = dp.isLandscape ? dp.heightPx : dp.widthPx;
+ float screenPart = (landscapeScreenHeight * SCREEN_UNITS);
+ float defaultDp = dpiFromPx(screenPart, DisplayMetrics.DENSITY_DEVICE_STABLE);
+ float thisDp = dpToPx(defaultDp);
+ float multiplier = ResourcesCompat.getFloat(r, multiplierDimen);
+ float value = (thisDp) * multiplier;
+
+ return Math.round(value);
+ }
+
+ /**
+ * Returns the threshold that determines if we should show taskbar.
+ */
+ public static int getFromNavThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_from_nav_threshold,
+ R.dimen.taskbar_nav_threshold_mult);
+ }
+
+ /**
+ * Returns the threshold that we start moving the app window.
+ */
+ public static int getAppWindowThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_app_window_threshold,
+ R.dimen.taskbar_app_window_threshold_mult);
+ }
+
+ /**
+ * Returns the threshold for whether we land in home or overview.
+ */
+ public static int getHomeOverviewThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_home_overview_threshold,
+ R.dimen.taskbar_home_overview_threshold_mult);
+ }
+
+ /**
+ * Returns the threshold that we use to allow swipe to catch up to finger.
+ */
+ public static int getCatchUpThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_catch_up_threshold,
+ R.dimen.taskbar_catch_up_threshold_mult);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
index 065d111..916b1e6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -26,8 +26,8 @@
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.util.DisplayController;
@@ -92,6 +92,10 @@
mControllers.stashedHandleViewController.setTranslationYForSwipe(transY);
mControllers.taskbarViewController.setTranslationYForSwipe(transY);
mControllers.taskbarDragLayerController.setTranslationYForSwipe(transY);
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ controllers.bubbleBarViewController.setTranslationYForSwipe(transY);
+ controllers.bubbleStashedHandleViewController.setTranslationYForSwipe(transY);
+ });
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 7154731..6fad655 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -40,8 +40,11 @@
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.systemui.shared.recents.model.Task;
import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.stream.Stream;
/**
@@ -204,9 +207,10 @@
return;
}
- recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
- splitSelectSource.itemInfo.getComponentKey(),
- foundTask -> {
+ recentsView.getSplitSelectController().findLastActiveTasksAndRunCallback(
+ Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
+ foundTasks -> {
+ @Nullable Task foundTask = foundTasks.get(0);
splitSelectSource.alreadyRunningTaskId = foundTask == null
? INVALID_TASK_ID
: foundTask.key.id;
@@ -221,9 +225,10 @@
*/
public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
RecentsView recents = getRecentsView();
- recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
- info.getComponentKey(),
- foundTask -> {
+ recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
+ Collections.singletonList(info.getComponentKey()),
+ foundTasks -> {
+ @Nullable Task foundTask = foundTasks.get(0);
if (foundTask != null) {
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
// TODO (b/266482558): This additional null check is needed because there
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index bf3b932..fa5a1ae 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.content.Context;
@@ -87,7 +88,7 @@
private @Nullable IconButtonView mAllAppsButton;
// Only non-null when device supports having an All Apps button.
- private @Nullable View mTaskbarDivider;
+ private @Nullable IconButtonView mTaskbarDivider;
private View mQsb;
@@ -157,8 +158,12 @@
mActivityContext.getColor(R.color.all_apps_button_color));
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
- mTaskbarDivider = LayoutInflater.from(context).inflate(R.layout.taskbar_divider,
+ mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
+ R.layout.taskbar_divider,
this, false);
+ mTaskbarDivider.setIconDrawable(
+ resources.getDrawable(R.drawable.taskbar_divider_button));
+ mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
}
}
@@ -319,6 +324,9 @@
}
}
setClickAndLongClickListenersForIcon(hotseatView);
+ if (ENABLE_CURSOR_HOVER_STATES.get()) {
+ setHoverListenerForIcon(hotseatView);
+ }
nextViewIndex++;
}
// Remove remaining views
@@ -366,16 +374,18 @@
icon.setOnLongClickListener(mIconLongClickListener);
}
+ /**
+ * Sets OnHoverListener for the given view.
+ */
+ private void setHoverListenerForIcon(View icon) {
+ icon.setOnHoverListener(mControllerCallbacks.getIconOnHoverListener(icon));
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int count = getChildCount();
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
int spaceNeeded = getIconLayoutWidth();
- // We are removing the margin from taskbar divider item in taskbar,
- // so remove it from spacing also.
- if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && count > 1) {
- spaceNeeded -= mIconTouchSize;
- }
int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
boolean layoutRtl = isLayoutRtl();
int centerAlignIconEnd = right - (right - left - spaceNeeded) / 2;
@@ -496,7 +506,15 @@
if (deviceProfile.isQsbInline) {
countExcludingQsb--;
}
- return countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
+ int iconLayoutBoundsWidth =
+ countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
+
+ if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && countExcludingQsb > 1) {
+ // We are removing 4 * mItemMarginLeftRight as there should be no space between
+ // All Apps icon, divider icon, and first app icon in taskbar
+ iconLayoutBoundsWidth -= mItemMarginLeftRight * 4;
+ }
+ return iconLayoutBoundsWidth;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 4abd995..3d22e78 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -22,9 +24,8 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
@@ -45,6 +46,7 @@
import androidx.core.graphics.ColorUtils;
import androidx.core.view.OneShotPreDrawListener;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
@@ -53,7 +55,6 @@
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
@@ -133,7 +134,8 @@
mTaskbarView = taskbarView;
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
mTaskbarIconAlpha.setUpdateVisibility(true);
- mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
+ mModelCallbacks = TaskbarModelCallbacksFactory.newInstance(mActivity)
+ .create(mActivity, mTaskbarView);
mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin;
mStashedHandleHeight = activity.getResources()
.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height);
@@ -170,6 +172,13 @@
.getTaskbarNavButtonTranslationYForInAppDisplay();
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
+
+ if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
+ // This gets modified in NavbarButtonsViewController, but the initial value it reads
+ // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
+ mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
+ .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
+ }
}
/**
@@ -443,8 +452,13 @@
* Creates an animation for aligning the Taskbar icons with the provided Launcher device profile
*/
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
- mOnControllerPreCreateCallback.run();
PendingAnimation setter = new PendingAnimation(100);
+ if (TaskbarManager.isPhoneButtonNavMode(mActivity)) {
+ // No animation for icons in small-screen
+ return setter.createPlaybackController();
+ }
+
+ mOnControllerPreCreateCallback.run();
DeviceProfile taskbarDp = mActivity.getDeviceProfile();
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
@@ -676,6 +690,11 @@
.updateAndAnimateIsManuallyStashedInApp(true);
}
+ /** Gets the hover listener for the provided icon view. */
+ public View.OnHoverListener getIconOnHoverListener(View icon) {
+ return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
+ }
+
/**
* Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to
* consume the touch so TaskbarView treats it as an ACTION_CANCEL.
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
index b4b83f6..5d91acd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
@@ -47,9 +47,9 @@
}
@Override
- protected View inflateSearchBox() {
+ protected View inflateSearchBar() {
if (isSearchSupported()) {
- return super.inflateSearchBox();
+ return super.inflateSearchBar();
}
// Remove top padding of header, since we do not have any search
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index e0a502b..d786d94 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -15,11 +15,17 @@
*/
package com.android.launcher3.taskbar.allapps;
+import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.taskbar.TaskbarControllers;
@@ -30,7 +36,6 @@
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
-
/**
* Handles the all apps overlay window initialization, updates, and its data.
* <p>
@@ -45,13 +50,16 @@
public final class TaskbarAllAppsController {
private TaskbarControllers mControllers;
+ private @Nullable TaskbarOverlayContext mOverlayContext;
private @Nullable TaskbarAllAppsSlideInView mSlideInView;
private @Nullable TaskbarAllAppsContainerView mAppsView;
+ private @Nullable TaskbarSearchSessionController mSearchSessionController;
// Application data models.
- private AppInfo[] mApps;
+ private @NonNull AppInfo[] mApps = EMPTY_ARRAY;
private int mAppsModelFlags;
- private List<ItemInfo> mPredictedApps;
+ private @NonNull List<ItemInfo> mPredictedApps = Collections.emptyList();
+ private @Nullable List<ItemInfo> mZeroStateSearchSuggestions;
private boolean mDisallowGlobalDrag;
private boolean mDisallowLongClick;
@@ -70,9 +78,14 @@
}
}
+ /** Clean up the controller. */
+ public void onDestroy() {
+ cleanUpOverlay();
+ }
+
/** Updates the current {@link AppInfo} instances. */
- public void setApps(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
- mApps = apps;
+ public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
+ mApps = apps == null ? EMPTY_ARRAY : apps;
mAppsModelFlags = flags;
mPackageUserKeytoUidMap = map;
if (mAppsView != null) {
@@ -96,6 +109,14 @@
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
}
+ if (mSearchSessionController != null) {
+ mSearchSessionController.setZeroStatePredictedItems(predictedApps);
+ }
+ }
+
+ /** Updates the current search suggestions. */
+ public void setZeroStateSearchSuggestions(List<ItemInfo> zeroStateSearchSuggestions) {
+ mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
}
/** Updates the current notification dots. */
@@ -127,20 +148,28 @@
// to catch invalid states.
mControllers.getSharedState().allAppsVisible = true;
- TaskbarOverlayContext overlayContext =
- mControllers.taskbarOverlayController.requestWindow();
- mSlideInView = (TaskbarAllAppsSlideInView) overlayContext.getLayoutInflater().inflate(
- R.layout.taskbar_all_apps, overlayContext.getDragLayer(), false);
+ mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
+
+ // Initialize search session for All Apps.
+ mSearchSessionController = TaskbarSearchSessionController.newInstance(mOverlayContext);
+ mOverlayContext.setSearchSessionController(mSearchSessionController);
+ mSearchSessionController.setZeroStatePredictedItems(mPredictedApps);
+ if (mZeroStateSearchSuggestions != null) {
+ mSearchSessionController.setZeroStateSearchSuggestions(mZeroStateSearchSuggestions);
+ }
+ mSearchSessionController.startLifecycle();
+
+ mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate(
+ R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false);
mSlideInView.addOnCloseListener(() -> {
mControllers.getSharedState().allAppsVisible = false;
- mSlideInView = null;
- mAppsView = null;
+ cleanUpOverlay();
});
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
- overlayContext, mSlideInView, mControllers);
+ mOverlayContext, mSlideInView, mControllers, mSearchSessionController);
viewController.show(animate);
- mAppsView = overlayContext.getAppsView();
+ mAppsView = mOverlayContext.getAppsView();
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
mAppsView.getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
@@ -149,8 +178,28 @@
// Create a shared drag layer between taskbar and taskbarAllApps so that when dragging
// starts and taskbarAllApps can close, but the drag layer that the view is being dragged in
// doesn't also close
- overlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
- overlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
+ mOverlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
+ mOverlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
+ }
+
+ private void cleanUpOverlay() {
+ // Floating search bar is added to the drag layer in ActivityAllAppsContainerView onAttach;
+ // removed here as this is a special case that we remove the all apps panel.
+ if (mAppsView != null && mOverlayContext != null
+ && mAppsView.getSearchUiDelegate().isSearchBarFloating()) {
+ mOverlayContext.getDragLayer().removeView(mAppsView.getSearchView());
+ mAppsView.getSearchUiDelegate().onDestroySearchBar();
+ }
+ if (mSearchSessionController != null) {
+ mSearchSessionController.onDestroy();
+ mSearchSessionController = null;
+ }
+ if (mOverlayContext != null) {
+ mOverlayContext.setSearchSessionController(null);
+ mOverlayContext = null;
+ }
+ mSlideInView = null;
+ mAppsView = null;
}
@VisibleForTesting
@@ -164,4 +213,12 @@
// Allow null-pointer since this should only be null if the apps view is not showing.
return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset();
}
+
+ /** @see TaskbarSearchSessionController#createPreDragConditionForSearch(View) */
+ @Nullable
+ public PreDragCondition createPreDragConditionForSearch(View view) {
+ return mSearchSessionController != null
+ ? mSearchSessionController.createPreDragConditionForSearch(view)
+ : null;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index cfa1027..001c3bc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -15,20 +15,27 @@
*/
package com.android.launcher3.taskbar.allapps;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
-import android.animation.PropertyValuesHolder;
+import android.animation.Animator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
import android.view.animation.Interpolator;
import android.window.OnBackInvokedDispatcher;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
@@ -37,8 +44,11 @@
/** Wrapper for taskbar all apps with slide-in behavior. */
public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverlayContext>
implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
+ private final Handler mHandler;
+
private TaskbarAllAppsContainerView mAppsView;
private float mShiftRange;
+ private @Nullable Runnable mShowOnFullyAttachedToWindowRunnable;
// Initialized in init.
private TaskbarAllAppsCallbacks mAllAppsCallbacks;
@@ -50,6 +60,7 @@
public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mHandler = new Handler(Looper.myLooper());
}
void init(TaskbarAllAppsCallbacks callbacks) {
@@ -58,20 +69,51 @@
/** Opens the all apps view. */
void show(boolean animate) {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
return;
}
mIsOpen = true;
- attachToContainer();
- if (animate) {
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(EMPHASIZED);
- mOpenCloseAnimator.setDuration(mAllAppsCallbacks.getOpenDuration()).start();
- } else {
+ addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ removeOnAttachStateChangeListener(this);
+ // Wait for view and its descendants to be fully attached before starting open.
+ mShowOnFullyAttachedToWindowRunnable = () -> showOnFullyAttachedToWindow(animate);
+ mHandler.post(mShowOnFullyAttachedToWindowRunnable);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ removeOnAttachStateChangeListener(this);
+ }
+ });
+ attachToContainer();
+ }
+
+ private void showOnFullyAttachedToWindow(boolean animate) {
+ mAllAppsCallbacks.onAllAppsTransitionStart(true);
+ if (!animate) {
+ mAllAppsCallbacks.onAllAppsTransitionEnd(true);
mTranslationShift = TRANSLATION_SHIFT_OPENED;
+ return;
}
+
+ setUpOpenAnimation(mAllAppsCallbacks.getOpenDuration());
+ Animator animator = mOpenCloseAnimation.getAnimationPlayer();
+ animator.setInterpolator(EMPHASIZED);
+ animator.addListener(AnimatorListeners.forEndCallback(() -> {
+ if (mIsOpen) {
+ mAllAppsCallbacks.onAllAppsTransitionEnd(true);
+ }
+ }));
+ animator.start();
+ }
+
+ @Override
+ protected void onOpenCloseAnimationPending(PendingAnimation animation) {
+ mAllAppsCallbacks.onAllAppsAnimationPending(
+ animation, mToTranslationShift == TRANSLATION_SHIFT_OPENED);
}
/** The apps container inside this view. */
@@ -81,10 +123,23 @@
@Override
protected void handleClose(boolean animate) {
+ if (mShowOnFullyAttachedToWindowRunnable != null) {
+ mHandler.removeCallbacks(mShowOnFullyAttachedToWindowRunnable);
+ mShowOnFullyAttachedToWindowRunnable = null;
+ }
+ if (mIsOpen) {
+ mAllAppsCallbacks.onAllAppsTransitionStart(false);
+ }
handleClose(animate, mAllAppsCallbacks.getCloseDuration());
}
@Override
+ protected void onCloseComplete() {
+ mAllAppsCallbacks.onAllAppsTransitionEnd(false);
+ super.onCloseComplete();
+ }
+
+ @Override
protected Interpolator getIdleInterpolator() {
return EMPHASIZED;
}
@@ -194,4 +249,11 @@
protected boolean isEventOverContent(MotionEvent ev) {
return getPopupContainer().isEventOverView(mAppsView.getVisibleContainerView(), ev);
}
+
+ @Override
+ public void onBackInvoked() {
+ if (!mAllAppsCallbacks.handleSearchBackInvoked()) {
+ super.onBackInvoked();
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index 01342af..85633e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -19,8 +19,9 @@
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.allapps.AllAppsTransitionListener;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.appprediction.AppsDividerView;
-import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.taskbar.NavbarButtonsViewController;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarStashController;
@@ -44,7 +45,8 @@
TaskbarAllAppsViewController(
TaskbarOverlayContext context,
TaskbarAllAppsSlideInView slideInView,
- TaskbarControllers taskbarControllers) {
+ TaskbarControllers taskbarControllers,
+ TaskbarSearchSessionController searchSessionController) {
mContext = context;
mSlideInView = slideInView;
@@ -53,8 +55,7 @@
mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
mOverlayController = taskbarControllers.taskbarOverlayController;
- mSlideInView.init(new TaskbarAllAppsCallbacks());
- setUpIconLongClick();
+ mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
setUpAppDivider();
setUpTaskbarStashing();
}
@@ -69,15 +70,6 @@
mSlideInView.close(animate);
}
- private void setUpIconLongClick() {
- mAppsView.setOnIconLongClickListener(
- mContext.getDragController()::startDragOnLongClick);
- mAppsView.getFloatingHeaderView()
- .findFixedRowByType(PredictionRowView.class)
- .setOnIconLongClickListener(
- mContext.getDragController()::startDragOnLongClick);
- }
-
private void setUpAppDivider() {
mAppsView.getFloatingHeaderView()
.findFixedRowByType(AppsDividerView.class)
@@ -105,7 +97,13 @@
});
}
- class TaskbarAllAppsCallbacks {
+ class TaskbarAllAppsCallbacks implements AllAppsTransitionListener {
+ private final TaskbarSearchSessionController mSearchSessionController;
+
+ private TaskbarAllAppsCallbacks(TaskbarSearchSessionController searchSessionController) {
+ mSearchSessionController = searchSessionController;
+ }
+
int getOpenDuration() {
return mOverlayController.getOpenDuration();
}
@@ -113,5 +111,24 @@
int getCloseDuration() {
return mOverlayController.getCloseDuration();
}
+
+ @Override
+ public void onAllAppsTransitionStart(boolean toAllApps) {
+ mSearchSessionController.onAllAppsTransitionStart(toAllApps);
+ }
+
+ @Override
+ public void onAllAppsTransitionEnd(boolean toAllApps) {
+ mSearchSessionController.onAllAppsTransitionEnd(toAllApps);
+ }
+
+ /** Invoked on back press, returning {@code true} if the search session handled it. */
+ boolean handleSearchBackInvoked() {
+ return mSearchSessionController.handleBackInvoked();
+ }
+
+ void onAllAppsAnimationPending(PendingAnimation animation, boolean toAllApps) {
+ mSearchSessionController.onAllAppsAnimationPending(animation, toAllApps);
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
new file mode 100644
index 0000000..8a2041f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.taskbar.allapps
+
+import android.content.Context
+import android.view.View
+import com.android.launcher3.R
+import com.android.launcher3.allapps.AllAppsTransitionListener
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.dragndrop.DragOptions.PreDragCondition
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.util.ResourceBasedOverride
+import com.android.launcher3.util.ResourceBasedOverride.Overrides
+
+/** Stub for managing the Taskbar search session. */
+open class TaskbarSearchSessionController : ResourceBasedOverride, AllAppsTransitionListener {
+
+ /** Start the search session lifecycle. */
+ open fun startLifecycle() = Unit
+
+ /** Destroy the search session. */
+ open fun onDestroy() = Unit
+
+ /** Updates the predicted items shown in the zero-state. */
+ open fun setZeroStatePredictedItems(items: List<ItemInfo>) = Unit
+
+ /** Updates the search suggestions shown in the zero-state. */
+ open fun setZeroStateSearchSuggestions(items: List<ItemInfo>) = Unit
+
+ override fun onAllAppsTransitionStart(toAllApps: Boolean) = Unit
+
+ override fun onAllAppsTransitionEnd(toAllApps: Boolean) = Unit
+
+ /** Creates a [PreDragCondition] for [view], if it is a search result that requires one. */
+ open fun createPreDragConditionForSearch(view: View): PreDragCondition? = null
+
+ open fun handleBackInvoked(): Boolean = false
+
+ open fun onAllAppsAnimationPending(animation: PendingAnimation, toAllApps: Boolean) = Unit
+
+ companion object {
+ @JvmStatic
+ fun newInstance(context: Context): TaskbarSearchSessionController {
+ if (!FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ return TaskbarSearchSessionController()
+ }
+
+ return Overrides.getObject(
+ TaskbarSearchSessionController::class.java,
+ context,
+ R.string.taskbar_search_session_controller_class,
+ )
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 7397159..1e3f4f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -21,10 +21,10 @@
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.graphics.drawable.ShapeDrawable
+import com.android.app.animation.Interpolators
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapToRange
-import com.android.launcher3.anim.Interpolators
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.wm.shell.common.TriangleShape
@@ -48,6 +48,8 @@
private var showingArrow: Boolean = false
private var arrowDrawable: ShapeDrawable
+ var width: Float = 0f
+
init {
paint.color = context.getColor(R.color.taskbar_background)
paint.flags = Paint.ANTI_ALIAS_FLAG
@@ -59,8 +61,11 @@
pointerSize = res.getDimension(R.dimen.bubblebar_pointer_size)
shadowAlpha =
- if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
- else LIGHT_THEME_SHADOW_ALPHA
+ if (Utilities.isDarkTheme(context)) {
+ DARK_THEME_SHADOW_ALPHA
+ } else {
+ LIGHT_THEME_SHADOW_ALPHA
+ }
arrowDrawable =
ShapeDrawable(TriangleShape.create(pointerSize, pointerSize, /* pointUp= */ true))
@@ -102,7 +107,7 @@
// Draw background.
val radius = backgroundHeight / 2f
canvas.drawRoundRect(
- 0f,
+ canvas.width.toFloat() - width,
0f,
canvas.width.toFloat(),
canvas.height.toFloat(),
@@ -132,4 +137,8 @@
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
+
+ fun setArrowAlpha(alpha: Int) {
+ arrowDrawable.paint.alpha = alpha
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 6d19692..24db380 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -31,18 +31,25 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static java.lang.Math.abs;
+
import android.annotation.BinderThread;
import android.annotation.Nullable;
+import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
import android.os.Bundle;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -51,6 +58,8 @@
import android.util.PathParser;
import android.view.LayoutInflater;
+import androidx.appcompat.content.res.AppCompatResources;
+
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
@@ -112,8 +121,10 @@
private final Executor mMainExecutor;
private final LauncherApps mLauncherApps;
private final BubbleIconFactory mIconFactory;
+ private final SystemUiProxy mSystemUiProxy;
- private BubbleBarBubble mSelectedBubble;
+ private BubbleBarItem mSelectedBubble;
+ private BubbleBarOverflow mOverflowBubble;
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
@@ -152,8 +163,10 @@
mContext = context;
mBarView = bubbleView; // Need the view for inflating bubble views.
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
+
if (BUBBLE_BAR_ENABLED) {
- SystemUiProxy.INSTANCE.get(context).setBubblesListener(this);
+ mSystemUiProxy.setBubblesListener(this);
}
mMainExecutor = MAIN_EXECUTOR;
mLauncherApps = context.getSystemService(LauncherApps.class);
@@ -166,7 +179,7 @@
}
public void onDestroy() {
- SystemUiProxy.INSTANCE.get(mContext).setBubblesListener(null);
+ mSystemUiProxy.setBubblesListener(null);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -177,10 +190,32 @@
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
+ mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
+ key -> setSelectedBubble(mBubbles.get(key)));
});
}
/**
+ * Creates and adds the overflow bubble to the bubble bar if it hasn't been created yet.
+ *
+ * <p>This should be called on the {@link #BUBBLE_STATE_EXECUTOR} executor to avoid inflating
+ * the overflow multiple times.
+ */
+ private void createAndAddOverflowIfNeeded() {
+ if (mOverflowBubble == null) {
+ BubbleBarOverflow overflow = createOverflow(mContext);
+ mMainExecutor.execute(() -> {
+ // we're on the main executor now, so check that the overflow hasn't been created
+ // again to avoid races.
+ if (mOverflowBubble == null) {
+ mBubbleBarViewController.addBubble(overflow);
+ mOverflowBubble = overflow;
+ }
+ });
+ }
+ }
+
+ /**
* Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
*/
public void updateStateForSysuiFlags(int flags) {
@@ -209,18 +244,23 @@
|| !update.currentBubbleList.isEmpty()) {
// We have bubbles to load
BUBBLE_STATE_EXECUTOR.execute(() -> {
+ createAndAddOverflowIfNeeded();
if (update.addedBubble != null) {
- viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
+ viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
+ null /* existingBubble */);
}
if (update.updatedBubble != null) {
+ BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
viewUpdate.updatedBubble =
- populateBubble(update.updatedBubble, mContext, mBarView);
+ populateBubble(mContext, update.updatedBubble, mBarView,
+ existingBubble);
}
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
for (int i = 0; i < update.currentBubbleList.size(); i++) {
BubbleBarBubble b =
- populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
+ populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
+ null /* existingBubble */);
currentBubbles.add(b);
}
viewUpdate.currentBubbles = currentBubbles;
@@ -237,6 +277,7 @@
private void applyViewChanges(BubbleBarViewUpdate update) {
final boolean isCollapsed = (update.expandedChanged && !update.expanded)
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
+ BubbleBarItem previouslySelectedBubble = mSelectedBubble;
BubbleBarBubble bubbleToSelect = null;
if (!update.removedBubbles.isEmpty()) {
for (int i = 0; i < update.removedBubbles.size(); i++) {
@@ -281,10 +322,17 @@
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
+ if (mBubbles.isEmpty()) {
+ // all bubbles were removed. clear the selected bubble
+ mSelectedBubble = null;
+ }
+
if (update.updatedBubble != null) {
- // TODO: (b/269670235) handle updates:
- // (1) if content / icons change -- requires reload & add back in place
- // (2) if showing update dot changes -- tell the view to hide / show the dot
+ // Updates mean the dot state may have changed; any other changes were updated in
+ // the populateBubble step.
+ BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
+ // If we're not stashed, we're visible so animate
+ bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
@@ -301,8 +349,8 @@
// TODO: (b/273316505) handle suppression
}
if (update.selectedBubbleKey != null) {
- if (mSelectedBubble != null
- && !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
+ if (mSelectedBubble == null
+ || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
if (newlySelected != null) {
bubbleToSelect = newlySelected;
@@ -314,7 +362,11 @@
}
if (bubbleToSelect != null) {
setSelectedBubble(bubbleToSelect);
+ if (previouslySelectedBubble == null) {
+ mBubbleStashController.animateToInitialState(update.expanded);
+ }
}
+
if (update.expandedChanged) {
if (update.expanded != mBubbleBarViewController.isExpanded()) {
mBubbleBarViewController.setExpandedFromSysui(update.expanded);
@@ -324,12 +376,38 @@
}
}
+ /** Tells WMShell to show the currently selected bubble. */
+ public void showSelectedBubble() {
+ if (getSelectedBubbleKey() != null) {
+ if (mSelectedBubble instanceof BubbleBarBubble) {
+ // Because we've visited this bubble, we should suppress the notification.
+ // This is updated on WMShell side when we show the bubble, but that update isn't
+ // passed to launcher, instead we apply it directly here.
+ BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
+ info.setFlags(
+ info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ mSelectedBubble.getView().updateDotVisibility(true /* animate */);
+ }
+ mSystemUiProxy.showBubble(getSelectedBubbleKey(),
+ getBubbleBarOffsetX(), getBubbleBarOffsetY());
+ } else {
+ Log.w(TAG, "Trying to show the selected bubble but it's null");
+ }
+ }
+
+ /** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
+ public void showAndSelectBubble(BubbleBarItem b) {
+ if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey());
+ setSelectedBubble(b);
+ showSelectedBubble();
+ }
+
/**
* Sets the bubble that should be selected. This notifies the views, it does not notify
- * WMShell that the selection has changed, that should go through
- * {@link SystemUiProxy#showBubble}.
+ * WMShell that the selection has changed, that should go through either
+ * {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
*/
- public void setSelectedBubble(BubbleBarBubble b) {
+ private void setSelectedBubble(BubbleBarItem b) {
if (!Objects.equals(b, mSelectedBubble)) {
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
mSelectedBubble = b;
@@ -353,7 +431,8 @@
//
@Nullable
- private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
+ private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
+ @Nullable BubbleBarBubble existingBubble) {
String appName;
Bitmap badgeBitmap;
Bitmap bubbleBitmap;
@@ -422,16 +501,68 @@
iconPath.transform(matrix);
dotPath = iconPath;
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
- Color.WHITE, WHITE_SCRIM_ALPHA);
+ Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
+ if (existingBubble == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ BubbleView bubbleView = (BubbleView) inflater.inflate(
+ R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
+ BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
+ badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
+ bubbleView.setBubble(bubble);
+ return bubble;
+ } else {
+ // If we already have a bubble (so it already has an inflated view), update it.
+ existingBubble.setInfo(b);
+ existingBubble.setBadge(badgeBitmap);
+ existingBubble.setIcon(bubbleBitmap);
+ existingBubble.setDotColor(dotColor);
+ existingBubble.setDotPath(dotPath);
+ existingBubble.setAppName(appName);
+ return existingBubble;
+ }
+ }
+
+ private BubbleBarOverflow createOverflow(Context context) {
+ Bitmap bitmap = createOverflowBitmap(context);
LayoutInflater inflater = LayoutInflater.from(context);
BubbleView bubbleView = (BubbleView) inflater.inflate(
- R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
+ R.layout.bubblebar_item_view, mBarView, false /* attachToRoot */);
+ BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView);
+ bubbleView.setOverflow(overflow, bitmap);
+ return overflow;
+ }
- BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
- badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
- bubbleView.setBubble(bubble);
- return bubble;
+ private Bitmap createOverflowBitmap(Context context) {
+ Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
+ R.drawable.bubble_ic_overflow_button);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[]{
+ com.android.internal.R.attr.materialColorOnPrimaryFixed,
+ com.android.internal.R.attr.materialColorPrimaryFixed
+ });
+ int overflowIconColor = ta.getColor(0, Color.WHITE);
+ int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
+ ta.recycle();
+
+ iconDrawable.setTint(overflowIconColor);
+
+ int inset = context.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset);
+ Drawable foreground = new InsetDrawable(iconDrawable, inset);
+ Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor),
+ foreground);
+
+ return mIconFactory.createBadgedIconBitmap(drawable).icon;
+ }
+
+ private int getBubbleBarOffsetY() {
+ final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
+ return translation + mBarView.getHeight();
+ }
+
+ private int getBubbleBarOffsetX() {
+ return mBarView.getWidth() + mBarView.getHorizontalMargin();
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
similarity index 65%
rename from quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
rename to quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 3cd5f75..43e21f4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -19,16 +19,19 @@
import android.graphics.Path
import com.android.wm.shell.common.bubbles.BubbleInfo
+/** An entity in the bubble bar. */
+sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
+
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
data class BubbleBarBubble(
- val info: BubbleInfo,
- val view: BubbleView,
- val badge: Bitmap,
- val icon: Bitmap,
- val dotColor: Int,
- val dotPath: Path,
- val appName: String
-) {
+ var info: BubbleInfo,
+ override var view: BubbleView,
+ var badge: Bitmap,
+ var icon: Bitmap,
+ var dotColor: Int,
+ var dotPath: Path,
+ var appName: String
+) : BubbleBarItem(info.key, view)
- val key: String = info.key
-}
+/** Represents the overflow bubble in the bubble bar. */
+data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 0e1e0e1..c482911 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar.bubbles;
+import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
@@ -31,6 +32,7 @@
import com.android.launcher3.views.ActivityContext;
import java.util.List;
+import java.util.function.Consumer;
/**
* The view that holds all the bubble views. Modifying this view should happen through
@@ -66,11 +68,15 @@
// if it's smaller than 5.
private static final int MAX_BUBBLES = 5;
private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
+ private static final int WIDTH_ANIMATION_DURATION_MS = 200;
- private final TaskbarActivityContext mActivityContext;
private final BubbleBarBackground mBubbleBarBackground;
- // The current bounds of all the bubble bar.
+ /**
+ * The current bounds of all the bubble bar. Note that these bounds may not account for
+ * translation. The bounds should be retrieved using {@link #getBubbleBarBounds()} which
+ * updates the bounds and accounts for translation.
+ */
private final Rect mBubbleBarBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
@@ -89,6 +95,12 @@
private View.OnClickListener mOnClickListener;
private final Rect mTempRect = new Rect();
+ private float mRelativePivotX = 1f;
+ private float mRelativePivotY = 1f;
+
+ // An animator that represents the expansion state of the bubble bar, where 0 corresponds to the
+ // collapsed state and 1 to the fully expanded state.
+ private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
// We don't reorder the bubbles when they are expanded as it could be jarring for the user
// this runnable will be populated with any reordering of the bubbles that should be applied
@@ -96,6 +108,12 @@
@Nullable
private Runnable mReorderRunnable;
+ @Nullable
+ private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
+
+ @Nullable
+ private BubbleView mDraggedBubbleView;
+
public BubbleBarView(Context context) {
this(context, null);
}
@@ -110,7 +128,7 @@
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mActivityContext = ActivityContext.lookupContext(context);
+ TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
@@ -118,9 +136,46 @@
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
setClipToPadding(false);
- mBubbleBarBackground = new BubbleBarBackground(mActivityContext,
+ mBubbleBarBackground = new BubbleBarBackground(activityContext,
getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
setBackgroundDrawable(mBubbleBarBackground);
+
+ mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
+ mWidthAnimator.addUpdateListener(animation -> {
+ updateChildrenRenderNodeProperties();
+ invalidate();
+ });
+ mWidthAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBubbleBarBackground.showArrow(mIsBarExpanded);
+ if (!mIsBarExpanded && mReorderRunnable != null) {
+ mReorderRunnable.run();
+ mReorderRunnable = null;
+ }
+ // If the bar was just collapsed and the overflow was the last bubble that was
+ // selected, set the first bubble as selected.
+ if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
+ && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
+ BubbleView firstBubble = (BubbleView) getChildAt(0);
+ mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
+ }
+ updateWidth();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBubbleBarBackground.showArrow(true);
+ }
+ });
}
@Override
@@ -131,63 +186,157 @@
mBubbleBarBounds.right = right;
mBubbleBarBounds.bottom = bottom;
- // The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
- setPivotX(getWidth());
- setPivotY(getHeight());
+ // The bubble bar handle is aligned according to the relative pivot,
+ // by default it's aligned to the bottom edge of the screen so scale towards that
+ setPivotX(mRelativePivotX * getWidth());
+ setPivotY(mRelativePivotY * getHeight());
// Position the views
updateChildrenRenderNodeProperties();
}
/**
- * Returns the bounds of the bubble bar.
+ * Updates the bounds with translation that may have been applied and returns the result.
*/
public Rect getBubbleBarBounds() {
+ mBubbleBarBounds.top = getTop() + (int) getTranslationY();
+ mBubbleBarBounds.bottom = getBottom() + (int) getTranslationY();
return mBubbleBarBounds;
}
- // TODO: (b/273592694) animate it
+ /**
+ * Set bubble bar relative pivot value for X and Y, applied as a fraction of view width/height
+ * respectively. If the value is not in range of 0 to 1 it will be normalized.
+ * @param x relative X pivot value in range 0..1
+ * @param y relative Y pivot value in range 0..1
+ */
+ public void setRelativePivot(float x, float y) {
+ mRelativePivotX = Float.max(Float.min(x, 1), 0);
+ mRelativePivotY = Float.max(Float.min(y, 1), 0);
+ requestLayout();
+ }
+
+ /**
+ * Get current relative pivot for X axis
+ */
+ public float getRelativePivotX() {
+ return mRelativePivotX;
+ }
+
+ /**
+ * Get current relative pivot for Y axis
+ */
+ public float getRelativePivotY() {
+ return mRelativePivotY;
+ }
+
+ // TODO: (b/280605790) animate it
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() + 1 > MAX_BUBBLES) {
- removeViewInLayout(getChildAt(getChildCount() - 1));
+ // the last child view is the overflow bubble and we shouldn't remove that. remove the
+ // second to last child view.
+ removeViewInLayout(getChildAt(getChildCount() - 2));
}
super.addView(child, index, params);
+ updateWidth();
+ }
+
+ // TODO: (b/283309949) animate it
+ @Override
+ public void removeView(View view) {
+ super.removeView(view);
+ updateWidth();
+ }
+
+ private void updateWidth() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
+ setLayoutParams(lp);
+ }
+
+ /** @return the horizontal margin between the bubble bar and the edge of the screen. */
+ int getHorizontalMargin() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ return lp.getMarginEnd();
}
/**
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
* on the expanded state.
*/
- // TODO: (b/273592694) animate it
private void updateChildrenRenderNodeProperties() {
+ final float widthState = (float) mWidthAnimator.getAnimatedValue();
+ final float currentWidth = getWidth();
+ final float expandedWidth = expandedWidth();
+ final float collapsedWidth = collapsedWidth();
int bubbleCount = getChildCount();
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+ final boolean animate = getVisibility() == VISIBLE;
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
bv.setTranslationY(ty);
+
+ // the position of the bubble when the bar is fully expanded
+ final float expandedX = i * (mIconSize + mIconSpacing);
+ // the position of the bubble when the bar is fully collapsed
+ final float collapsedX = i == 0 ? 0 : mIconOverlapAmount;
+
if (mIsBarExpanded) {
- final float tx = i * (mIconSize + mIconSpacing);
- bv.setTranslationX(tx);
- bv.setZ(0);
- bv.showBadge();
+ // where the bubble will end up when the animation ends
+ final float targetX = currentWidth - expandedWidth + expandedX;
+ bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
+ // if we're fully expanded, set the z level to 0 or to bubble elevation if dragged
+ if (widthState == 1f) {
+ bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
+ }
+ // When we're expanded, we're not stacked so we're not behind the stack
+ bv.setBehindStack(false, animate);
+ bv.setAlpha(1);
} else {
+ final float targetX = currentWidth - collapsedWidth + collapsedX;
+ bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
- bv.setTranslationX(i * mIconOverlapAmount);
- if (i > 0) {
- bv.hideBadge();
- } else {
- bv.showBadge();
+ // If we're not the first bubble we're behind the stack
+ bv.setBehindStack(i > 0, animate);
+ // If we're fully collapsed, hide all bubbles except for the first 2. If there are
+ // only 2 bubbles, hide the second bubble as well because it's the overflow.
+ if (widthState == 0) {
+ if (i > 1) {
+ bv.setAlpha(0);
+ } else if (i == 1 && bubbleCount == 2) {
+ bv.setAlpha(0);
+ }
}
}
}
+
+ // update the arrow position
+ final float collapsedArrowPosition = arrowPositionForSelectedWhenCollapsed();
+ final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded();
+ final float interpolatedWidth =
+ widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
+ if (mIsBarExpanded) {
+ // when the bar is expanding, the selected bubble is always the first, so the arrow
+ // always shifts with the interpolated width.
+ final float arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
+ mBubbleBarBackground.setArrowPosition(arrowPosition);
+ } else {
+ final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
+ final float arrowPosition =
+ targetPosition + widthState * (expandedArrowPosition - targetPosition);
+ mBubbleBarBackground.setArrowPosition(arrowPosition);
+ }
+
+ mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
+ mBubbleBarBackground.setWidth(interpolatedWidth);
}
/**
* Reorders the views to match the provided list.
*/
public void reorder(List<BubbleView> viewOrder) {
- if (isExpanded()) {
+ if (isExpanded() || mWidthAnimator.isRunning()) {
mReorderRunnable = () -> doReorder(viewOrder);
} else {
doReorder(viewOrder);
@@ -199,7 +348,10 @@
if (!isExpanded()) {
for (int i = 0; i < viewOrder.size(); i++) {
View child = viewOrder.get(i);
- if (child != null) {
+ // this child view may have already been removed so verify that it still exists
+ // before reordering it, otherwise it will be re-added.
+ int indexOfChild = indexOfChild(child);
+ if (child != null && indexOfChild >= 0) {
removeViewInLayout(child);
addViewInLayout(child, i, child.getLayoutParams());
}
@@ -208,6 +360,11 @@
}
}
+ public void setUpdateSelectedBubbleAfterCollapse(
+ Consumer<String> updateSelectedBubbleAfterCollapse) {
+ mUpdateSelectedBubbleAfterCollapse = updateSelectedBubbleAfterCollapse;
+ }
+
/**
* Sets which bubble view should be shown as selected.
*/
@@ -217,6 +374,14 @@
}
/**
+ * Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top
+ */
+ public void setDraggedBubble(@Nullable BubbleView view) {
+ mDraggedBubbleView = view;
+ requestLayout();
+ }
+
+ /**
* Update the arrow position to match the selected bubble.
*
* @param shouldAnimate whether or not to animate the arrow. If the bar was just expanded, this
@@ -247,6 +412,16 @@
}
}
+ private float arrowPositionForSelectedWhenExpanded() {
+ final int index = indexOfChild(mSelectedBubbleView);
+ return getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
+ }
+
+ private float arrowPositionForSelectedWhenCollapsed() {
+ final int index = indexOfChild(mSelectedBubbleView);
+ return getPaddingStart() + index * (mIconOverlapAmount) + mIconSize / 2f;
+ }
+
@Override
public void setOnClickListener(View.OnClickListener listener) {
mOnClickListener = listener;
@@ -264,18 +439,16 @@
/**
* Sets whether the bubble bar is expanded or collapsed.
*/
- // TODO: (b/273592694) animate it
public void setExpanded(boolean isBarExpanded) {
if (mIsBarExpanded != isBarExpanded) {
mIsBarExpanded = isBarExpanded;
updateArrowForSelected(/* shouldAnimate= */ false);
setOrUnsetClickListener();
- if (!isBarExpanded && mReorderRunnable != null) {
- mReorderRunnable.run();
- mReorderRunnable = null;
+ if (isBarExpanded) {
+ mWidthAnimator.start();
+ } else {
+ mWidthAnimator.reverse();
}
- mBubbleBarBackground.showArrow(mIsBarExpanded);
- requestLayout(); // trigger layout to reposition views & update size for expansion
}
}
@@ -286,19 +459,20 @@
return mIsBarExpanded;
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ private float expandedWidth() {
final int childCount = getChildCount();
- final float iconWidth = mIsBarExpanded
- ? (childCount * (mIconSize + mIconSpacing))
- : mIconSize + ((childCount - 1) * mIconOverlapAmount);
- final int totalWidth = (int) iconWidth + getPaddingStart() + getPaddingEnd();
- setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
+ final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+ return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
+ }
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- measureChild(child, (int) mIconSize, (int) mIconSize);
- }
+ private float collapsedWidth() {
+ final int childCount = getChildCount();
+ final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+ // If there are more than 2 bubbles, the first 2 should be visible when collapsed.
+ // Otherwise just the first bubble should be visible because we don't show the overflow.
+ return childCount > 2
+ ? mIconSize + mIconOverlapAmount + horizontalPadding
+ : mIconSize + horizontalPadding;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 82494c6..20b8e3b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -24,16 +24,21 @@
import android.view.View;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarInsetsController;
+import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.SystemUiProxy;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as
@@ -51,6 +56,9 @@
// Initialized in init.
private BubbleStashController mBubbleStashController;
private BubbleBarController mBubbleBarController;
+ private BubbleDragController mBubbleDragController;
+ private TaskbarStashController mTaskbarStashController;
+ private TaskbarInsetsController mTaskbarInsetsController;
private View.OnClickListener mBubbleClickListener;
private View.OnClickListener mBubbleBarClickListener;
@@ -80,6 +88,9 @@
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
+ mBubbleDragController = bubbleControllers.bubbleDragController;
+ mTaskbarStashController = controllers.taskbarStashController;
+ mTaskbarInsetsController = controllers.taskbarInsetsController;
mActivity.addOnDeviceProfileChangeListener(dp ->
mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight
@@ -88,12 +99,15 @@
mBubbleBarScale.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked(v);
mBubbleBarClickListener = v -> setExpanded(true);
+ mBubbleDragController.setupBubbleBarView(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
- // TODO: when barView layout changes tell taskbarInsetsController the insets have changed.
+ mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ );
}
private void onBubbleClicked(View v) {
- BubbleBarBubble bubble = ((BubbleView) v).getBubble();
+ BubbleBarItem bubble = ((BubbleView) v).getBubble();
if (bubble == null) {
Log.e(TAG, "bubble click listener, bubble was null");
}
@@ -103,9 +117,7 @@
setExpanded(false);
mBubbleStashController.stashBubbleBar();
} else {
- mBubbleBarController.setSelectedBubble(bubble);
- mSystemUiProxy.showBubble(bubble.getKey(),
- mBubbleStashController.isBubblesShowingOnHome());
+ mBubbleBarController.showAndSelectBubble(bubble);
}
}
@@ -133,6 +145,11 @@
return mBarView.getVisibility() == VISIBLE;
}
+ /** Whether the bubble bar has bubbles. */
+ public boolean hasBubbles() {
+ return mBubbleBarController.getSelectedBubbleKey() != null;
+ }
+
/**
* The bounds of the bubble bar.
*/
@@ -140,6 +157,11 @@
return mBarView.getBubbleBarBounds();
}
+ /** The horizontal margin of the bubble bar from the edge of the screen. */
+ public int getHorizontalMargin() {
+ return mBarView.getHorizontalMargin();
+ }
+
/**
* When the bubble bar is not stashed, it can be collapsed (the icons are in a stack) or
* expanded (the icons are in a row). This indicates whether the bubble bar is expanded.
@@ -176,6 +198,12 @@
}
}
+ /** Sets a callback that updates the selected bubble after the bubble bar collapses. */
+ public void setUpdateSelectedBubbleAfterCollapse(
+ Consumer<String> updateSelectedBubbleAfterCollapse) {
+ mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse);
+ }
+
/**
* Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
*/
@@ -188,10 +216,12 @@
// TODO: (b/273592694) animate it
private void updateVisibilityForStateChange() {
- if (!mHiddenForSysui && !mBubbleStashController.isStashed() && !mHiddenForNoBubbles) {
+ if (!mHiddenForSysui && !mHiddenForNoBubbles) {
mBarView.setVisibility(VISIBLE);
} else {
mBarView.setVisibility(INVISIBLE);
+ mBarView.setAlpha(0);
+ mBarView.setExpanded(false);
}
}
@@ -228,7 +258,7 @@
/**
* Removes the provided bubble from the bubble bar.
*/
- public void removeBubble(BubbleBarBubble b) {
+ public void removeBubble(BubbleBarItem b) {
if (b != null) {
mBarView.removeView(b.getView());
} else {
@@ -239,10 +269,11 @@
/**
* Adds the provided bubble to the bubble bar.
*/
- public void addBubble(BubbleBarBubble b) {
+ public void addBubble(BubbleBarItem b) {
if (b != null) {
mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
b.getView().setOnClickListener(mBubbleClickListener);
+ mBubbleDragController.setupBubbleView(b.getView());
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
@@ -260,7 +291,7 @@
/**
* Updates the selected bubble.
*/
- public void updateSelectedBubble(BubbleBarBubble newlySelected) {
+ public void updateSelectedBubble(BubbleBarItem newlySelected) {
mBarView.setSelectedBubble(newlySelected.getView());
}
@@ -276,14 +307,9 @@
if (!isExpanded) {
mSystemUiProxy.collapseBubbles();
} else {
- final String selectedKey = mBubbleBarController.getSelectedBubbleKey();
- if (selectedKey != null) {
- mSystemUiProxy.showBubble(selectedKey,
- mBubbleStashController.isBubblesShowingOnHome());
- } else {
- Log.w(TAG, "trying to expand bubbles when there isn't one selected");
- }
- // TODO: Tell taskbar stash controller to stash without bubbles following
+ mBubbleBarController.showSelectedBubble();
+ mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */,
+ false /* shouldBubblesFollow */);
}
}
}
@@ -299,4 +325,46 @@
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
}
}
+
+ /**
+ * Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
+ * that a bubble is being dragged to dismiss.
+ * @param bubbleView dragged bubble view
+ */
+ public void onDragStart(@NonNull BubbleView bubbleView) {
+ if (bubbleView.getBubble() == null) return;
+ mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true);
+ mBarView.setDraggedBubble(bubbleView);
+ }
+
+ /**
+ * Notifies SystemUI to expand the selected bubble when the bubble is released.
+ * @param bubbleView dragged bubble view
+ */
+ public void onDragRelease(@NonNull BubbleView bubbleView) {
+ if (bubbleView.getBubble() == null) return;
+ mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false);
+ }
+
+ /**
+ * Removes the dragged bubble view in the bubble bar view
+ */
+ public void onDragEnd() {
+ mBarView.setDraggedBubble(null);
+ }
+
+ /**
+ * Called when bubble was dragged into the dismiss target. Notifies System
+ * @param bubble dismissed bubble item
+ */
+ public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
+ mSystemUiProxy.removeBubble(bubble.getKey());
+ }
+
+ /**
+ * Called when bubble stack was dragged into the dismiss target
+ */
+ public void onDismissAllBubblesWhileDragging() {
+ mSystemUiProxy.removeAllBubbles();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 6417f3c..c47427d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -27,6 +27,8 @@
public final BubbleBarViewController bubbleBarViewController;
public final BubbleStashController bubbleStashController;
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
+ public final BubbleDragController bubbleDragController;
+ public final BubbleDismissController bubbleDismissController;
private final RunnableList mPostInitRunnables = new RunnableList();
@@ -39,11 +41,15 @@
BubbleBarController bubbleBarController,
BubbleBarViewController bubbleBarViewController,
BubbleStashController bubbleStashController,
- BubbleStashedHandleViewController bubbleStashedHandleViewController) {
+ BubbleStashedHandleViewController bubbleStashedHandleViewController,
+ BubbleDragController bubbleDragController,
+ BubbleDismissController bubbleDismissController) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
+ this.bubbleDragController = bubbleDragController;
+ this.bubbleDismissController = bubbleDismissController;
}
/**
@@ -56,6 +62,8 @@
bubbleBarViewController.init(taskbarControllers, this);
bubbleStashedHandleViewController.init(taskbarControllers, this);
bubbleStashController.init(taskbarControllers, this);
+ bubbleDragController.init(/* bubbleControllers = */ this);
+ bubbleDismissController.init(/* bubbleControllers = */ this);
mPostInitRunnables.executeAllAndDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
new file mode 100644
index 0000000..41c3dec
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
@@ -0,0 +1,206 @@
+/*
+ * 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.taskbar.bubbles;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarDragLayer;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
+/**
+ * Controls dismiss view presentation for the bubble bar dismiss functionality.
+ * Provides the dragged view snapping to the target dismiss area and animates it.
+ * When the dragged bubble/bubble stack is released inside of the target area, it gets dismissed.
+ *
+ * @see BubbleDragController
+ */
+public class BubbleDismissController {
+ private static final String TAG = BubbleDismissController.class.getSimpleName();
+ private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarDragLayer mDragLayer;
+ @Nullable
+ private BubbleBarViewController mBubbleBarViewController;
+
+ // Dismiss view that's attached to drag layer. It consists of the scrim view and the circular
+ // dismiss view used as a dismiss target.
+ @Nullable
+ private DismissView mDismissView;
+
+ // The currently magnetized object, which is being dragged and will be attracted to the magnetic
+ // dismiss target. This is either the stack itself, or an individual bubble.
+ @Nullable
+ private MagnetizedObject<View> mMagnetizedObject;
+
+ // The MagneticTarget instance for our circular dismiss view. This is added to the
+ // MagnetizedObject instances for the stack and any dragged-out bubbles.
+ @Nullable
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ // The bubble drag animator that synchronizes bubble drag and dismiss view animations
+ // A new instance is provided when the dismiss view is setup
+ @Nullable
+ private BubbleDragAnimator mAnimator;
+
+ public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
+ mActivity = activity;
+ mDragLayer = dragLayer;
+ }
+
+ /**
+ * Initializes dependencies when bubble controllers are created.
+ * Should be careful to only access things that were created in constructors for now, as some
+ * controllers may still be waiting for init().
+ */
+ public void init(@NonNull BubbleControllers bubbleControllers) {
+ mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
+ }
+
+ /**
+ * Setup the dismiss view and magnetized object that will be attracted to magnetic target.
+ * Should be called before handling events or showing/hiding dismiss view.
+ *
+ * @param magnetizedView the view to be pulled into target dismiss area
+ * @param animator the bubble animator to be used for the magnetized view, it syncs bubble
+ * dragging and dismiss animations with the dismiss view provided.
+ */
+ public void setupDismissView(@NonNull View magnetizedView,
+ @NonNull BubbleDragAnimator animator) {
+ setupDismissView();
+ setupMagnetizedObject(magnetizedView);
+ if (mDismissView != null) {
+ animator.setDismissView(mDismissView);
+ mAnimator = animator;
+ }
+ }
+
+ /**
+ * Handle the touch event and pass it to the magnetized object.
+ * It should be called after {@code setupDismissView}
+ */
+ public boolean handleTouchEvent(@NonNull MotionEvent event) {
+ return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * Show dismiss view with animation
+ * It should be called after {@code setupDismissView}
+ */
+ public void showDismissView() {
+ if (mDismissView == null) return;
+ mDismissView.show();
+ }
+
+ /**
+ * Hide dismiss view with animation
+ * It should be called after {@code setupDismissView}
+ */
+ public void hideDismissView() {
+ if (mDismissView == null) return;
+ mDismissView.hide();
+ }
+
+ /**
+ * Dismiss magnetized object when it's released in the dismiss target area
+ */
+ private void dismissMagnetizedObject() {
+ if (mMagnetizedObject == null || mBubbleBarViewController == null) return;
+ if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
+ BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
+ if (bubbleView.getBubble() != null) {
+ mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
+ }
+ } else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
+ mBubbleBarViewController.onDismissAllBubblesWhileDragging();
+ }
+ }
+
+ private void setupDismissView() {
+ if (mDismissView != null) return;
+ mDismissView = new DismissView(mActivity.getApplicationContext());
+ BubbleDismissViewUtils.setup(mDismissView);
+ mDragLayer.addView(mDismissView, /* index = */ 0,
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mDismissView.setElevation(mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_elevation));
+ setupMagneticTarget(mDismissView.getCircle());
+ }
+
+ private void setupMagneticTarget(@NonNull View view) {
+ int magneticFieldRadius = mActivity.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_size);
+ mMagneticTarget = new MagnetizedObject.MagneticTarget(view, magneticFieldRadius);
+ }
+
+ private void setupMagnetizedObject(@NonNull View magnetizedView) {
+ mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
+ magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
+ @Override
+ public float getWidth(@NonNull View underlyingObject) {
+ return underlyingObject.getWidth() * underlyingObject.getScaleX();
+ }
+
+ @Override
+ public float getHeight(@NonNull View underlyingObject) {
+ return underlyingObject.getHeight() * underlyingObject.getScaleY();
+ }
+
+ @Override
+ public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+ };
+
+ mMagnetizedObject.setHapticsEnabled(true);
+ mMagnetizedObject.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ if (mMagneticTarget != null) {
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ } else {
+ Log.e(TAG,"Requires MagneticTarget to add target to MagnetizedObject!");
+ }
+ mMagnetizedObject.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ if (mAnimator == null) return;
+ mAnimator.animateDismissCaptured();
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ if (mAnimator == null) return;
+ mAnimator.animateDismissReleased();
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ dismissMagnetizedObject();
+ }
+ });
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
new file mode 100644
index 0000000..4b235a9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+@file:JvmName("BubbleDismissViewUtils")
+
+package com.android.launcher3.taskbar.bubbles
+
+import com.android.launcher3.R
+import com.android.wm.shell.common.bubbles.DismissView
+
+/**
+ * Dismiss view is shared from WMShell. It requires setup with local resources.
+ *
+ * Usage:
+ * - Kotlin `dismissView.setup()`
+ * - Java `BubbleDismissViewUtils.setup(dismissView)`
+ */
+fun DismissView.setup() {
+ setup(
+ DismissView.Config(
+ targetSizeResId = R.dimen.bubblebar_dismiss_target_size,
+ iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
+ bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
+ floatingGradientHeightResId = R.dimen.bubblebar_dismiss_floating_gradient_height,
+ floatingGradientColorResId = android.R.color.system_neutral1_900,
+ backgroundResId = R.drawable.bg_bubble_dismiss_circle,
+ iconResId = R.drawable.ic_bubble_dismiss_white
+ )
+ )
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
new file mode 100644
index 0000000..24dca5e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
@@ -0,0 +1,222 @@
+/*
+ * 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.taskbar.bubbles;
+
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
+import com.android.launcher3.R;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
+
+/**
+ * The animator performs the bubble animations while dragging and coordinates bubble and dismiss
+ * view animations when it gets magnetized, released or dismissed.
+ */
+public class BubbleDragAnimator {
+ private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
+ private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
+ private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
+
+ private final PhysicsAnimator.SpringConfig mDefaultConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
+ private final PhysicsAnimator.SpringConfig mTranslationConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
+ @NonNull
+ private final View mView;
+ @NonNull
+ private final PhysicsAnimator<View> mBubbleAnimator;
+ @Nullable
+ private DismissView mDismissView;
+ @Nullable
+ private PhysicsAnimator<DismissCircleView> mDismissAnimator;
+ private final float mBubbleFocusedScale;
+ private final float mBubbleCapturedScale;
+ private final float mDismissCapturedScale;
+
+ /**
+ * Should be initialised for each dragged view
+ *
+ * @param view the dragged view to animate
+ */
+ public BubbleDragAnimator(@NonNull View view) {
+ mView = view;
+ mBubbleAnimator = PhysicsAnimator.getInstance(view);
+ mBubbleAnimator.setDefaultSpringConfig(mDefaultConfig);
+
+ Resources resources = view.getResources();
+ final int collapsedSize = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_small_size);
+ final int expandedSize = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_size);
+ mDismissCapturedScale = (float) collapsedSize / expandedSize;
+
+ if (view instanceof BubbleBarView) {
+ mBubbleFocusedScale = SCALE_BUBBLE_BAR_FOCUSED;
+ mBubbleCapturedScale = mDismissCapturedScale;
+ } else {
+ mBubbleFocusedScale = SCALE_BUBBLE_FOCUSED;
+ mBubbleCapturedScale = SCALE_BUBBLE_CAPTURED;
+ }
+ }
+
+ /**
+ * Sets dismiss view to be animated alongside the dragged bubble
+ */
+ public void setDismissView(@NonNull DismissView dismissView) {
+ mDismissView = dismissView;
+ mDismissAnimator = PhysicsAnimator.getInstance(dismissView.getCircle());
+ mDismissAnimator.setDefaultSpringConfig(mDefaultConfig);
+ }
+
+ /**
+ * Animates the focused state of the bubble when the dragging starts
+ */
+ public void animateFocused() {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
+ .spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
+ .start();
+ }
+
+ /**
+ * Animates the dragged bubble movement back to the initial position.
+ *
+ * @param initialPosition the position to animate to
+ * @param velocity the initial velocity to use for the spring animation
+ * @param endActions gets called when the animation completes or gets cancelled
+ */
+ public void animateToInitialState(@NonNull PointF initialPosition, @NonNull PointF velocity,
+ @Nullable Runnable endActions) {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, 1f)
+ .spring(DynamicAnimation.SCALE_Y, 1f)
+ .spring(DynamicAnimation.TRANSLATION_X, initialPosition.x, velocity.x,
+ mTranslationConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, initialPosition.y, velocity.y,
+ mTranslationConfig)
+ .addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
+ boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
+ boolean allRelevantPropertyAnimationsEnded) -> {
+ if (canceled || allRelevantPropertyAnimationsEnded) {
+ resetAnimatedViews(initialPosition);
+ if (endActions != null) {
+ endActions.run();
+ }
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Animates the dragged view alongside the dismiss view when it gets captured in the dismiss
+ * target area.
+ */
+ public void animateDismissCaptured() {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, mBubbleCapturedScale)
+ .spring(DynamicAnimation.SCALE_Y, mBubbleCapturedScale)
+ .spring(DynamicAnimation.ALPHA, mDismissCapturedScale)
+ .start();
+
+ if (mDismissAnimator != null) {
+ mDismissAnimator.cancel();
+ mDismissAnimator
+ .spring(DynamicAnimation.SCALE_X, mDismissCapturedScale)
+ .spring(DynamicAnimation.SCALE_Y, mDismissCapturedScale)
+ .start();
+ }
+ }
+
+ /**
+ * Animates the dragged view alongside the dismiss view when it gets released from the dismiss
+ * target area.
+ */
+ public void animateDismissReleased() {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
+ .spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
+ .spring(DynamicAnimation.ALPHA, 1f)
+ .start();
+
+ if (mDismissAnimator != null) {
+ mDismissAnimator.cancel();
+ mDismissAnimator
+ .spring(DynamicAnimation.SCALE_X, 1f)
+ .spring(DynamicAnimation.SCALE_Y, 1f)
+ .start();
+ }
+ }
+
+ /**
+ * Animates the dragged bubble dismiss when it's released in the dismiss target area.
+ *
+ * @param initialPosition the initial position to move the bubble too after animation finishes
+ * @param endActions gets called when the animation completes or gets cancelled
+ */
+ public void animateDismiss(@NonNull PointF initialPosition, @Nullable Runnable endActions) {
+ float dismissHeight = mDismissView != null ? mDismissView.getHeight() : 0f;
+ float translationY = mView.getTranslationY() + dismissHeight;
+ mBubbleAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y, translationY)
+ .spring(DynamicAnimation.SCALE_X, 0f)
+ .spring(DynamicAnimation.SCALE_Y, 0f)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
+ boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
+ boolean allRelevantPropertyAnimationsEnded) -> {
+ if (canceled || allRelevantPropertyAnimationsEnded) {
+ resetAnimatedViews(initialPosition);
+ if (endActions != null) endActions.run();
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Reset the animated views to the initial state
+ *
+ * @param initialPosition position of the bubble
+ */
+ private void resetAnimatedViews(@NonNull PointF initialPosition) {
+ mView.setScaleX(1f);
+ mView.setScaleY(1f);
+ mView.setAlpha(1f);
+ mView.setTranslationX(initialPosition.x);
+ mView.setTranslationY(initialPosition.y);
+
+ if (mDismissView != null) {
+ mDismissView.getCircle().setScaleX(1f);
+ mDismissView.getCircle().setScaleY(1f);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
new file mode 100644
index 0000000..08fd681
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -0,0 +1,355 @@
+/*
+ * 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.taskbar.bubbles;
+
+import android.annotation.SuppressLint;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+
+/**
+ * Controls bubble bar drag to dismiss interaction.
+ * Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
+ * Supported interactions:
+ * - Drag a single bubble view into dismiss target to remove it.
+ * - Drag the bubble stack into dismiss target to remove all.
+ * Restores initial position of dragged view if released outside of the dismiss target.
+ */
+public class BubbleDragController {
+ private final TaskbarActivityContext mActivity;
+ private BubbleBarViewController mBubbleBarViewController;
+ private BubbleDismissController mBubbleDismissController;
+
+ public BubbleDragController(TaskbarActivityContext activity) {
+ mActivity = activity;
+ }
+
+ /**
+ * Initializes dependencies when bubble controllers are created.
+ * Should be careful to only access things that were created in constructors for now, as some
+ * controllers may still be waiting for init().
+ */
+ public void init(@NonNull BubbleControllers bubbleControllers) {
+ mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
+ mBubbleDismissController = bubbleControllers.bubbleDismissController;
+ }
+
+ /**
+ * Setup the bubble view for dragging and attach touch listener to it
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ public void setupBubbleView(@NonNull BubbleView bubbleView) {
+ if (!(bubbleView.getBubble() instanceof BubbleBarBubble)) {
+ // Don't setup dragging for overflow bubble view
+ return;
+ }
+
+ bubbleView.setOnTouchListener(new BubbleTouchListener() {
+ @Override
+ void onDragStart() {
+ mBubbleBarViewController.onDragStart(bubbleView);
+ }
+
+ @Override
+ void onDragEnd() {
+ mBubbleBarViewController.onDragEnd();
+ }
+
+ @Override
+ protected void onDragRelease() {
+ mBubbleBarViewController.onDragRelease(bubbleView);
+ }
+ });
+ }
+
+ /**
+ * Setup the bubble bar view for dragging and attach touch listener to it
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
+ PointF initialRelativePivot = new PointF();
+ bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
+ @Override
+ protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
+ if (bubbleBarView.isExpanded()) return false;
+ return super.onTouchDown(view, event);
+ }
+
+ @Override
+ void onDragStart() {
+ initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
+ bubbleBarView.getRelativePivotY());
+ // By default the bubble bar view pivot is in bottom right corner, while dragging
+ // it should be centered in order to align it with the dismiss target view
+ bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
+ }
+
+ @Override
+ void onDragEnd() {
+ // Restoring the initial pivot for the bubble bar view
+ bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
+ }
+ });
+ }
+
+ /**
+ * Bubble touch listener for handling a single bubble view or bubble bar view while dragging.
+ * The dragging starts after "shorter" long click (the long click duration might change):
+ * - When the touch gesture moves out of the {@code ACTION_DOWN} location the dragging
+ * interaction is cancelled.
+ * - When {@code ACTION_UP} happens before long click is registered and there was no significant
+ * movement the view will perform click.
+ * - When the listener registers long click it starts dragging interaction, all the subsequent
+ * {@code ACTION_MOVE} events will drag the view, and the interaction finishes when
+ * {@code ACTION_UP} or {@code ACTION_CANCEL} are received.
+ * Lifecycle methods can be overridden do add extra setup/clean up steps.
+ */
+ private abstract class BubbleTouchListener implements View.OnTouchListener {
+ /**
+ * The internal state of the touch listener
+ */
+ private enum State {
+ // Idle and ready for the touch events.
+ // Changes to:
+ // - TOUCHED, when the {@code ACTION_DOWN} is handled
+ IDLE,
+
+ // Touch down was handled and the lister is recognising the gestures.
+ // Changes to:
+ // - IDLE, when performs the click
+ // - DRAGGING, when registers the long click and starts dragging interaction
+ // - CANCELLED, when the touch events move out of the initial location before the long
+ // click is recognised
+
+ TOUCHED,
+
+ // The long click was registered and the view is being dragged.
+ // Changes to:
+ // - IDLE, when the gesture ends with the {@code ACTION_UP} or {@code ACTION_CANCEL}
+ DRAGGING,
+
+ // The dragging was cancelled.
+ // Changes to:
+ // - IDLE, when the current gesture completes
+ CANCELLED
+ }
+
+ private final PointF mTouchDownLocation = new PointF();
+ private final PointF mViewInitialPosition = new PointF();
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout() / 2;
+ private State mState = State.IDLE;
+ private int mTouchSlop = -1;
+ private BubbleDragAnimator mAnimator;
+ @Nullable
+ private Runnable mLongClickRunnable;
+
+ /**
+ * Called when the dragging interaction has started
+ */
+ abstract void onDragStart();
+
+ /**
+ * Called when the dragging interaction has ended and all the animations have completed
+ */
+ abstract void onDragEnd();
+
+ /**
+ * Called when the dragged bubble is released outside of the dismiss target area and will
+ * move back to its initial position
+ */
+ protected void onDragRelease() {
+ }
+
+ /**
+ * Called when the dragged bubble is released inside of the dismiss target area and will get
+ * dismissed with animation
+ */
+ protected void onDragDismiss() {
+ }
+
+ @Override
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
+ updateVelocity(event);
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ return onTouchDown(view, event);
+ case MotionEvent.ACTION_MOVE:
+ onTouchMove(view, event);
+ break;
+ case MotionEvent.ACTION_UP:
+ onTouchUp(view, event);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ onTouchCancel(view, event);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * The touch down starts the interaction and schedules the long click handler.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ * @return true if the gesture should be intercepted and handled, false otherwise. Note if
+ * the false is returned subsequent events in the gesture won't get reported.
+ */
+ protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
+ mState = State.TOUCHED;
+ mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
+ mTouchDownLocation.set(event.getRawX(), event.getRawY());
+ mViewInitialPosition.set(view.getTranslationX(), view.getTranslationY());
+ setupLongClickHandler(view);
+ return true;
+ }
+
+ /**
+ * The move event drags the view or cancels the interaction if hasn't long clicked yet.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ */
+ protected void onTouchMove(@NonNull View view, @NonNull MotionEvent event) {
+ final float dx = event.getRawX() - mTouchDownLocation.x;
+ final float dy = event.getRawY() - mTouchDownLocation.y;
+ switch (mState) {
+ case TOUCHED:
+ final boolean movedOut = Math.hypot(dx, dy) > mTouchSlop;
+ if (movedOut) {
+ // Moved out of the initial location before the long click was registered
+ mState = State.CANCELLED;
+ cleanUpLongClickHandler(view);
+ }
+ break;
+ case DRAGGING:
+ drag(view, event, dx, dy);
+ break;
+ }
+ }
+
+ /**
+ * On touch up performs click or finishes the dragging depending on the state.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ */
+ protected void onTouchUp(@NonNull View view, @NonNull MotionEvent event) {
+ switch (mState) {
+ case TOUCHED:
+ view.performClick();
+ cleanUp(view);
+ break;
+ case DRAGGING:
+ stopDragging(view, event);
+ break;
+ default:
+ cleanUp(view);
+ break;
+ }
+ }
+
+ /**
+ * The gesture is cancelled and the interaction should clean up and complete.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ */
+ protected void onTouchCancel(@NonNull View view, @NonNull MotionEvent event) {
+ if (mState == State.DRAGGING) {
+ stopDragging(view, event);
+ } else {
+ cleanUp(view);
+ }
+ }
+
+ private void startDragging(@NonNull View view) {
+ onDragStart();
+ mActivity.setTaskbarWindowFullscreen(true);
+ mAnimator = new BubbleDragAnimator(view);
+ mAnimator.animateFocused();
+ mBubbleDismissController.setupDismissView(view, mAnimator);
+ mBubbleDismissController.showDismissView();
+ }
+
+ private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy) {
+ if (mBubbleDismissController.handleTouchEvent(event)) return;
+ view.setTranslationX(mViewInitialPosition.x + dx);
+ view.setTranslationY(mViewInitialPosition.y + dy);
+ }
+
+ private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
+ Runnable onComplete = () -> {
+ mActivity.setTaskbarWindowFullscreen(false);
+ cleanUp(view);
+ onDragEnd();
+ };
+
+ if (mBubbleDismissController.handleTouchEvent(event)) {
+ onDragDismiss();
+ mAnimator.animateDismiss(mViewInitialPosition, onComplete);
+ } else {
+ onDragRelease();
+ mAnimator.animateToInitialState(mViewInitialPosition, getCurrentVelocity(),
+ onComplete);
+ }
+ mBubbleDismissController.hideDismissView();
+ }
+
+ private void setupLongClickHandler(@NonNull View view) {
+ cleanUpLongClickHandler(view);
+ mLongClickRunnable = () -> {
+ // Register long click and start dragging interaction
+ mState = State.DRAGGING;
+ startDragging(view);
+ };
+ view.getHandler().postDelayed(mLongClickRunnable, mPressToDragTimeout);
+ }
+
+ private void cleanUpLongClickHandler(@NonNull View view) {
+ if (mLongClickRunnable == null || view.getHandler() == null) return;
+ view.getHandler().removeCallbacks(mLongClickRunnable);
+ mLongClickRunnable = null;
+ }
+
+ private void cleanUp(@NonNull View view) {
+ cleanUpLongClickHandler(view);
+ mVelocityTracker.clear();
+ mState = State.IDLE;
+ }
+
+ private void updateVelocity(MotionEvent event) {
+ final float deltaX = event.getRawX() - event.getX();
+ final float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ private PointF getCurrentVelocity() {
+ mVelocityTracker.computeCurrentVelocity(/* units = */ 1000);
+ return new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 0ab53b0..a5ea5a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar.bubbles;
+import static java.lang.Math.abs;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -25,6 +27,7 @@
import com.android.launcher3.taskbar.StashedHandleViewController;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.util.MultiPropertyFactory;
@@ -50,6 +53,7 @@
// Initialized in init.
private TaskbarControllers mControllers;
+ private TaskbarInsetsController mTaskbarInsetsController;
private BubbleBarViewController mBarViewController;
private BubbleStashedHandleViewController mHandleViewController;
private TaskbarStashController mTaskbarStashController;
@@ -67,6 +71,7 @@
private int mUnstashedHeight;
private boolean mBubblesShowingOnHome;
private boolean mBubblesShowingOnOverview;
+ private boolean mIsSysuiLocked;
@Nullable
private AnimatorSet mAnimator;
@@ -77,6 +82,7 @@
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mControllers = controllers;
+ mTaskbarInsetsController = controllers.taskbarInsetsController;
mBarViewController = bubbleControllers.bubbleBarViewController;
mHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
mTaskbarStashController = controllers.taskbarStashController;
@@ -90,14 +96,6 @@
mStashedHeight = mHandleViewController.getStashedHeight();
mUnstashedHeight = mHandleViewController.getUnstashedHeight();
-
- bubbleControllers.runAfterInit(() -> {
- if (mTaskbarStashController.isStashed()) {
- stashBubbleBar();
- } else {
- showBubbleBar(false /* expandBubbles */);
- }
- });
}
/**
@@ -115,13 +113,63 @@
}
/**
+ * Animates the bubble bar and handle to their initial state, transitioning from the state where
+ * both views are invisible. Called when the first bubble is added or when the device is
+ * unlocked.
+ *
+ * <p>Normally either the bubble bar or the handle is visible,
+ * and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to transition
+ * between these two states. But the transition from the state where both the bar and handle
+ * are invisible is slightly different.
+ *
+ * <p>The initial state will depend on the current state of the device, i.e. overview, home etc
+ * and whether bubbles are requested to be expanded.
+ */
+ public void animateToInitialState(boolean expanding) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ if (expanding || mBubblesShowingOnHome || mBubblesShowingOnOverview) {
+ mIsStashed = false;
+ animatorSet.playTogether(mIconScaleForStash.animateToValue(1),
+ mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()),
+ mIconAlphaForStash.animateToValue(1));
+ } else {
+ mIsStashed = true;
+ animatorSet.playTogether(mBubbleStashedHandleAlpha.animateToValue(1));
+ }
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onIsStashedChanged();
+ }
+ });
+ animatorSet.setDuration(BAR_STASH_DURATION).start();
+ }
+
+ /**
* Called when launcher enters or exits the home page. Bubbles are unstashed on home.
*/
public void setBubblesShowingOnHome(boolean onHome) {
if (mBubblesShowingOnHome != onHome) {
mBubblesShowingOnHome = onHome;
+
+ if (!mBarViewController.hasBubbles()) {
+ // if there are no bubbles, there's nothing to show, so just return.
+ return;
+ }
+
if (mBubblesShowingOnHome) {
showBubbleBar(/* expanded= */ false);
+ // When transitioning from app to home the stash animator may already have been
+ // created, so we need to animate the bubble bar here to align with hotseat.
+ if (!mIsStashed) {
+ mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForHotseat())
+ .start();
+ }
+ // If the bubble bar is already unstashed, the taskbar touchable region won't be
+ // updated correctly, so force an update here.
+ mControllers.runAfterInit(() ->
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
} else if (!mBarViewController.isExpanded()) {
stashBubbleBar();
}
@@ -140,18 +188,27 @@
mBubblesShowingOnOverview = onOverview;
if (!mBubblesShowingOnOverview && !mBarViewController.isExpanded()) {
stashBubbleBar();
+ } else {
+ // When transitioning to overview the stash animator may already have been
+ // created, so we need to animate the bubble bar here to align with taskbar.
+ mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForTaskbar())
+ .start();
}
}
}
+ /** Whether bubbles are showing on Overview. */
+ public boolean isBubblesShowingOnOverview() {
+ return mBubblesShowingOnOverview;
+ }
+
/** Called when sysui locked state changes, when locked, bubble bar is stashed. */
public void onSysuiLockedStateChange(boolean isSysuiLocked) {
- if (isSysuiLocked) {
- // TODO: should the normal path flip mBubblesOnHome / check if this is needed
- // If we're locked, we're no longer showing on home.
- mBubblesShowingOnHome = false;
- mBubblesShowingOnOverview = false;
- stashBubbleBar();
+ if (isSysuiLocked != mIsSysuiLocked) {
+ mIsSysuiLocked = isSysuiLocked;
+ if (!mIsSysuiLocked && mBarViewController.hasBubbles()) {
+ animateToInitialState(false /* expanding */);
+ }
}
}
@@ -230,9 +287,8 @@
firstHalfDurationScale = 0.5f;
secondHalfDurationScale = 0.75f;
- // If we're on home, adjust the translation so the bubble bar aligns with hotseat.
- final float hotseatTransY = mActivity.getDeviceProfile().getTaskbarOffsetY();
- final float translationY = mBubblesShowingOnHome ? hotseatTransY : 0;
+ final float translationY = getBubbleBarTranslationY();
+
fullLengthAnimatorSet.playTogether(
mIconScaleForStash.animateToValue(1),
mIconTranslationYForStash.animateToValue(translationY));
@@ -262,6 +318,7 @@
if (isStashed) {
mBarViewController.setExpanded(false);
}
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
});
}
});
@@ -271,7 +328,26 @@
private void onIsStashedChanged() {
mControllers.runAfterInit(() -> {
mHandleViewController.onIsStashedChanged();
- // TODO: when stash changes tell taskbarInsetsController the insets have changed.
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
});
}
+
+ private float getBubbleBarTranslationYForTaskbar() {
+ return -mActivity.getDeviceProfile().taskbarBottomMargin;
+ }
+
+ private float getBubbleBarTranslationYForHotseat() {
+ final float hotseatBottomSpace = mActivity.getDeviceProfile().hotseatBarBottomSpacePx;
+ final float hotseatCellHeight = mActivity.getDeviceProfile().hotseatCellHeightPx;
+ return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs(
+ hotseatCellHeight - mUnstashedHeight) / 2;
+ }
+
+ float getBubbleBarTranslationY() {
+ // If we're on home, adjust the translation so the bubble bar aligns with hotseat.
+ // Otherwise we're either showing in an app or in overview. In either case adjust it so
+ // the bubble bar aligns with the taskbar.
+ return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
+ : getBubbleBarTranslationYForTaskbar();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 2170a5d..fbab595 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -52,6 +52,7 @@
private BubbleStashController mBubbleStashController;
private RegionSamplingHelper mRegionSamplingHelper;
private int mBarSize;
+ private int mStashedTaskbarHeight;
private int mStashedHandleWidth;
private int mStashedHandleHeight;
@@ -92,7 +93,7 @@
mTaskbarStashedHandleAlpha.get(0).setValue(0);
- final int stashedTaskbarHeight = resources.getDimensionPixelSize(
+ mStashedTaskbarHeight = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_size);
mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
@Override
@@ -115,22 +116,27 @@
}
}, Executors.UI_HELPER_EXECUTOR);
- mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
- // As more bubbles get added, the icon bounds become larger. To ensure a consistent
- // handle bar position, we pin it to the edge of the screen.
- Rect bubblebarRect = mBarViewController.getBubbleBarBounds();
- final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+ mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
+ updateBounds());
+ }
- mStashedHandleBounds.set(
- bubblebarRect.right - mStashedHandleWidth,
- stashedCenterY - mStashedHandleHeight / 2,
- bubblebarRect.right,
- stashedCenterY + mStashedHandleHeight / 2);
- mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+ private void updateBounds() {
+ // As more bubbles get added, the icon bounds become larger. To ensure a consistent
+ // handle bar position, we pin it to the edge of the screen.
+ final int right =
+ mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
- view.setPivotX(view.getWidth());
- view.setPivotY(view.getHeight() - stashedTaskbarHeight / 2f);
- });
+ final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
+
+ mStashedHandleBounds.set(
+ right - mStashedHandleWidth,
+ stashedCenterY - mStashedHandleHeight / 2,
+ right,
+ stashedCenterY + mStashedHandleHeight / 2);
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+
+ mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
+ mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
}
public void onDestroy() {
@@ -188,6 +194,7 @@
mStashedHandleView.setVisibility(VISIBLE);
} else {
mStashedHandleView.setVisibility(INVISIBLE);
+ mStashedHandleView.setAlpha(0);
}
updateRegionSampling();
}
@@ -203,12 +210,14 @@
private void updateRegionSampling() {
boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE
&& mBubbleStashController.isStashed();
- mRegionSamplingHelper.setWindowVisible(handleVisible);
- if (handleVisible) {
- mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
- mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
- } else {
- mRegionSamplingHelper.stop();
+ if (mRegionSamplingHelper != null) {
+ mRegionSamplingHelper.setWindowVisible(handleVisible);
+ if (handleVisible) {
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+ mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
+ } else {
+ mRegionSamplingHelper.stop();
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index e22e63a..12cb8c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -18,7 +18,9 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,27 +30,59 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
+import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.wm.shell.animation.Interpolators;
+
+import java.util.EnumSet;
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
-// TODO: (b/269670235) currently this doesn't show the 'update dot'
+
/**
* View that displays a bubble icon, along with an app badge on either the left or
* right side of the view.
*/
public class BubbleView extends ConstraintLayout {
- // TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
public static final int DEFAULT_PATH_SIZE = 100;
+ /**
+ * Flags that suppress the visibility of the 'new' dot or the app badge, for one reason or
+ * another. If any of these flags are set, the dot will not be shown.
+ * If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
+ */
+ enum SuppressionFlag {
+ // TODO: (b/277815200) implement flyout
+ // Suppressed because the flyout is visible - it will morph into the dot via animation.
+ FLYOUT_VISIBLE,
+ // Suppressed because this bubble is behind others in the collapsed stack.
+ BEHIND_STACK,
+ }
+
+ private final EnumSet<SuppressionFlag> mSuppressionFlags =
+ EnumSet.noneOf(SuppressionFlag.class);
+
private final ImageView mBubbleIcon;
private final ImageView mAppIcon;
private final int mBubbleSize;
+ private DotRenderer mDotRenderer;
+ private DotRenderer.DrawParams mDrawParams;
+ private int mDotColor;
+ private Rect mTempBounds = new Rect();
+
+ // Whether the dot is animating
+ private boolean mDotIsAnimating;
+ // What scale value the dot is animating to
+ private float mAnimatingToDotScale;
+ // The current scale value of the dot
+ private float mDotScale;
+
// TODO: (b/273310265) handle RTL
+ // Whether the bubbles are positioned on the left or right side of the screen
private boolean mOnLeft = false;
- private BubbleBarBubble mBubble;
+ private BubbleBarItem mBubble;
public BubbleView(Context context) {
this(context, null);
@@ -74,6 +108,8 @@
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
+ mDrawParams = new DotRenderer.DrawParams();
+
setFocusable(true);
setClickable(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -90,45 +126,147 @@
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (!shouldDrawDot()) {
+ return;
+ }
+
+ getDrawingRect(mTempBounds);
+
+ mDrawParams.dotColor = mDotColor;
+ mDrawParams.iconBounds = mTempBounds;
+ mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.scale = mDotScale;
+
+ mDotRenderer.draw(canvas, mDrawParams);
+ }
+
/** Sets the bubble being rendered in this view. */
void setBubble(BubbleBarBubble bubble) {
mBubble = bubble;
mBubbleIcon.setImageBitmap(bubble.getIcon());
mAppIcon.setImageBitmap(bubble.getBadge());
+ mDotColor = bubble.getDotColor();
+ mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
+ }
+
+ /**
+ * Sets that this bubble represents the overflow. The overflow appears in the list of bubbles
+ * but does not represent app content, instead it shows recent bubbles that couldn't fit into
+ * the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
+ * come from an app.
+ */
+ void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
+ mBubble = overflow;
+ mBubbleIcon.setImageBitmap(bitmap);
+ mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
}
/** Returns the bubble being rendered in this view. */
@Nullable
- BubbleBarBubble getBubble() {
+ BubbleBarItem getBubble() {
return mBubble;
}
- /** Shows the app badge on this bubble. */
- void showBadge() {
- Bitmap appBadgeBitmap = mBubble.getBadge();
- if (appBadgeBitmap == null) {
- mAppIcon.setVisibility(GONE);
+ void updateDotVisibility(boolean animate) {
+ final float targetScale = shouldDrawDot() ? 1f : 0f;
+ if (animate) {
+ animateDotScale();
+ } else {
+ mDotScale = targetScale;
+ mAnimatingToDotScale = targetScale;
+ invalidate();
+ }
+ }
+
+ void updateBadgeVisibility() {
+ if (mBubble instanceof BubbleBarOverflow) {
+ // The overflow bubble does not have a badge, so just bail.
+ return;
+ }
+ BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
+ Bitmap appBadgeBitmap = bubble.getBadge();
+ int translationX = mOnLeft
+ ? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
+ : 0;
+ mAppIcon.setTranslationX(translationX);
+ mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
+ }
+
+ /** Sets whether this bubble is in the stack & not the first bubble. **/
+ void setBehindStack(boolean behindStack, boolean animate) {
+ if (behindStack) {
+ mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
+ } else {
+ mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
+ }
+ updateDotVisibility(animate);
+ updateBadgeVisibility();
+ }
+
+ /** Whether this bubble is in the stack & not the first bubble. **/
+ boolean isBehindStack() {
+ return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
+ }
+
+ /** Whether the dot indicating unseen content in a bubble should be shown. */
+ private boolean shouldDrawDot() {
+ boolean bubbleHasUnseenContent = mBubble != null
+ && mBubble instanceof BubbleBarBubble
+ && mSuppressionFlags.isEmpty()
+ && !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
+
+ // Always render the dot if it's animating, since it could be animating out. Otherwise, show
+ // it if the bubble wants to show it, and we aren't suppressing it.
+ return bubbleHasUnseenContent || mDotIsAnimating;
+ }
+
+ /** How big the dot should be, fraction from 0 to 1. */
+ private void setDotScale(float fraction) {
+ mDotScale = fraction;
+ invalidate();
+ }
+
+ /**
+ * Animates the dot to the given scale.
+ */
+ private void animateDotScale() {
+ float toScale = shouldDrawDot() ? 1f : 0f;
+ mDotIsAnimating = true;
+
+ // Don't restart the animation if we're already animating to the given value.
+ if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
+ mDotIsAnimating = false;
return;
}
- int translationX;
- if (mOnLeft) {
- translationX = -(mBubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
- } else {
- translationX = 0;
- }
+ mAnimatingToDotScale = toScale;
- mAppIcon.setTranslationX(translationX);
- mAppIcon.setVisibility(VISIBLE);
+ final boolean showDot = toScale > 0f;
+
+ // Do NOT wait until after animation ends to setShowDot
+ // to avoid overriding more recent showDot states.
+ clearAnimation();
+ animate()
+ .setDuration(200)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener((valueAnimator) -> {
+ float fraction = valueAnimator.getAnimatedFraction();
+ fraction = showDot ? fraction : 1f - fraction;
+ setDotScale(fraction);
+ }).withEndAction(() -> {
+ setDotScale(showDot ? 1f : 0f);
+ mDotIsAnimating = false;
+ }).start();
}
- /** Hides the app badge on this bubble. */
- void hideBadge() {
- mAppIcon.setVisibility(GONE);
- }
@Override
public String toString() {
- return "BubbleView{" + mBubble + "}";
+ String toString = mBubble != null ? mBubble.getKey() : "null";
+ return "BubbleView{" + toString + "}";
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
new file mode 100644
index 0000000..7af0389
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -0,0 +1 @@
+madym@google.com
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index 27a4988..b682081 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -17,10 +17,12 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.graphics.drawable.RotateDrawable
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.R
+import com.android.launcher3.Utilities
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
/**
@@ -40,7 +42,18 @@
protected val endContextualContainer: ViewGroup,
protected val startContextualContainer: ViewGroup
) : NavButtonLayoutter {
- protected val homeButton: ImageView = navButtonContainer.findViewById(R.id.home)
- protected val recentsButton: ImageView = navButtonContainer.findViewById(R.id.recent_apps)
- protected val backButton: ImageView = navButtonContainer.findViewById(R.id.back)
+ protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
+ protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
+ protected val backButton: ImageView? = navButtonContainer.findViewById(R.id.back)
+
+ init {
+ // setup back button drawable
+ if (backButton != null) {
+ val rotateDrawable = RotateDrawable()
+ rotateDrawable.drawable = backButton.context?.getDrawable(R.drawable.ic_sysbar_back)
+ rotateDrawable.fromDegrees = 0f
+ rotateDrawable.toDegrees = if (Utilities.isRtl(backButton.resources)) 90f else -90f
+ backButton.setImageDrawable(rotateDrawable)
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 468a1a7..4a53c0c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -51,10 +51,10 @@
val paddingTop = (buttonHeight - iconSize) / 2
// Update icons
- backButton.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
+ backButton!!.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
backButton.scaleType = ImageView.ScaleType.FIT_CENTER
backButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
- homeButton.setImageDrawable(homeButton.getContext().getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
+ homeButton!!.setImageDrawable(homeButton.context.getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
homeButton.scaleType = ImageView.ScaleType.FIT_CENTER
homeButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 0668da9..5c7f2be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -17,6 +17,8 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.view.Surface.ROTATION_90
+import android.view.Surface.Rotation
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
@@ -56,7 +58,8 @@
isKidsMode: Boolean,
isInSetup: Boolean,
isThreeButtonNav: Boolean,
- phoneMode: Boolean
+ phoneMode: Boolean,
+ @Rotation surfaceRotation: Int
): NavButtonLayoutter {
val navButtonContainer = navButtonsView.findViewById<LinearLayout>(ID_END_NAV_BUTTONS)
val endContextualContainer =
@@ -64,6 +67,7 @@
val startContextualContainer =
navButtonsView.findViewById<ViewGroup>(ID_START_CONTEXTUAL_BUTTONS)
val isPhoneNavMode = phoneMode && isThreeButtonNav
+ val isPhoneGestureMode = phoneMode && !isThreeButtonNav
return when {
isPhoneNavMode -> {
if (!deviceProfile.isLandscape) {
@@ -73,15 +77,30 @@
endContextualContainer,
startContextualContainer
)
- } else {
+ } else if (surfaceRotation == ROTATION_90) {
PhoneLandscapeNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
)
+ } else {
+ PhoneSeascapeNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
}
}
+ isPhoneGestureMode ->{
+ PhoneGestureLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
+ }
deviceProfile.isTaskbarPresent -> {
return when {
isInSetup -> {
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
new file mode 100644
index 0000000..8525c6c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+
+/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
+class PhoneGestureLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ AbstractNavButtonLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+ // no-op
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 201895f..2acd5d4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -27,7 +27,7 @@
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.util.DimensionUtils
-class PhoneLandscapeNavLayoutter(
+open class PhoneLandscapeNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
@@ -42,17 +42,15 @@
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
// TODO(b/230395757): Polish pending, this is just to make it usable
- val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- val taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
+ val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+ TaskbarManager.isPhoneMode(dp))
navButtonContainer.removeAllViews()
navButtonContainer.orientation = LinearLayout.VERTICAL
+ val navContainerParams = FrameLayout.LayoutParams(
+ taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
navContainerParams.apply {
- width = taskbarDimensions.x
- height = ViewGroup.LayoutParams.MATCH_PARENT
- gravity = Gravity.CENTER
topMargin = endStartMargins
bottomMargin = endStartMargins
marginEnd = 0
@@ -65,6 +63,7 @@
navButtonContainer.addView(backButton)
navButtonContainer.layoutParams = navContainerParams
+ navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
val spaceInBetween: Int =
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index f7ac974..c763115 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -41,27 +41,31 @@
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
// TODO(b/230395757): Polish pending, this is just to make it usable
- val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
val taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
+ DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+ TaskbarManager.isPhoneMode(dp))
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- navContainerParams.width = taskbarDimensions.x
- navContainerParams.height = ViewGroup.LayoutParams.MATCH_PARENT
- navContainerParams.gravity = Gravity.CENTER_VERTICAL
// Ensure order of buttons is correct
navButtonContainer.removeAllViews()
navButtonContainer.orientation = LinearLayout.HORIZONTAL
- navContainerParams.topMargin = 0
- navContainerParams.bottomMargin = 0
- navContainerParams.marginEnd = endStartMargins
- navContainerParams.marginStart = endStartMargins
+
+ val navContainerParams = FrameLayout.LayoutParams(
+ taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
+ navContainerParams.apply {
+ topMargin = 0
+ bottomMargin = 0
+ marginEnd = endStartMargins
+ marginStart = endStartMargins
+ }
+
// Swap recents and back button in case we were landscape prior to this
navButtonContainer.addView(backButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
navButtonContainer.layoutParams = navContainerParams
+ navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
val spaceInBetween =
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
new file mode 100644
index 0000000..f0fe581
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -0,0 +1,46 @@
+/*
+* 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.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+
+class PhoneSeascapeNavLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ PhoneLandscapeNavLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+ // TODO(b/230395757): Polish pending, this is just to make it usable
+ super.layoutButtons(dp, isContextualButtonShowing)
+ navButtonContainer.removeAllViews()
+ // Flip ordering of back and recents buttons
+ navButtonContainer.addView(backButton)
+ navButtonContainer.addView(homeButton)
+ navButtonContainer.addView(recentsButton)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 5ec7ca0..8332b7d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -40,7 +40,6 @@
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
// Add spacing after the end of the last nav button
- val navButtonParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
val contextualWidth = endContextualContainer.width
// If contextual buttons are showing, we check if the end margin is enough for the
@@ -50,10 +49,10 @@
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
}
+ val navButtonParams = FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
navButtonParams.apply {
- gravity = Gravity.END
- width = FrameLayout.LayoutParams.WRAP_CONTENT
- height = ViewGroup.LayoutParams.MATCH_PARENT
+ gravity = Gravity.END or Gravity.CENTER_VERTICAL
marginEnd = navMarginEnd
}
navButtonContainer.orientation = LinearLayout.HORIZONTAL
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index a642693..64cc47c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.dot.DotInfo;
@@ -29,6 +31,7 @@
import com.android.launcher3.taskbar.TaskbarDragController;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
+import com.android.launcher3.taskbar.allapps.TaskbarSearchSessionController;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
/**
@@ -47,6 +50,8 @@
private final int mStashedTaskbarHeight;
private final TaskbarUIController mUiController;
+ private @Nullable TaskbarSearchSessionController mSearchSessionController;
+
public TaskbarOverlayContext(
Context windowContext,
TaskbarActivityContext taskbarContext,
@@ -62,6 +67,15 @@
mUiController = controllers.uiController;
}
+ public @Nullable TaskbarSearchSessionController getSearchSessionController() {
+ return mSearchSessionController;
+ }
+
+ public void setSearchSessionController(
+ @Nullable TaskbarSearchSessionController searchSessionController) {
+ mSearchSessionController = searchSessionController;
+ }
+
int getStashedTaskbarHeight() {
return mStashedTaskbarHeight;
}
@@ -112,6 +126,11 @@
}
@Override
+ public View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return mDragController::startDragOnLongClick;
+ }
+
+ @Override
public PopupDataProvider getPopupDataProvider() {
return mTaskbarContext.getPopupDataProvider();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
index add7279..ff00560 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.graphics.Insets;
+import android.media.permission.SafeCloseable;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -29,6 +30,7 @@
import androidx.annotation.NonNull;
+import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -44,6 +46,7 @@
BaseDragLayer<TaskbarOverlayContext> implements
ViewTreeObserver.OnComputeInternalInsetsListener {
+ private SafeCloseable mViewCaptureCloseable;
private final List<OnClickListener> mOnClickListeners = new CopyOnWriteArrayList<>();
private final TouchController mClickListenerTouchController = new TouchController() {
@Override
@@ -77,12 +80,15 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
+ .startCapture(getRootView(), ".TaskbarOverlay");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mViewCaptureCloseable.close();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index a53dc15..475f465 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -16,13 +16,16 @@
package com.android.launcher3.uioverrides;
+import android.app.ActivityOptions;
import android.app.Person;
import android.content.Context;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.window.RemoteTransition;
import com.android.launcher3.Utilities;
+import com.android.quickstep.util.FadeOutRemoteTransition;
import java.util.Map;
@@ -41,4 +44,13 @@
public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
return context.getSystemService(LauncherApps.class).getActivityOverrides();
}
+
+ /**
+ * Creates an ActivityOptions to play fade-out animation on closing targets
+ */
+ public static ActivityOptions createFadeOutAnimOptions(Context context) {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
+ return options;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 955440b..d78ca88 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,40 +16,34 @@
package com.android.launcher3.uioverrides;
+import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.quickstep.views.FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
-import android.graphics.Rect;
-import android.graphics.RectF;
import android.util.FloatProperty;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.RecentsView;
/**
@@ -113,48 +107,19 @@
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
- if (mRecentsView.isSplitSelectionActive()) {
- // TODO (b/238651489): Refactor state management to avoid need for double check
- FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView();
- if (floatingTask != null) {
- // We are in split selection state currently, transitioning to another state
- DragLayer dragLayer = mLauncher.getDragLayer();
- RectF onScreenRectF = new RectF();
- Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), floatingTask,
- new Rect(0, 0, floatingTask.getWidth(), floatingTask.getHeight()),
- false, null, onScreenRectF);
- // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
- // on-screen portion)
- onScreenRectF.intersect(
- dragLayer.getLeft(),
- dragLayer.getTop(),
- dragLayer.getRight(),
- dragLayer.getBottom()
- );
-
- setter.setFloat(
- mRecentsView.getFirstFloatingTaskView(),
- PRIMARY_TRANSLATE_OFFSCREEN,
- mRecentsView.getPagedOrientationHandler()
- .getFloatingTaskOffscreenTranslationTarget(
- floatingTask,
- onScreenRectF,
- floatingTask.getStagePosition(),
- mLauncher.getDeviceProfile()
- ),
- config.getInterpolator(
- ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
- LINEAR
- ));
- setter.setViewAlpha(
- mRecentsView.getSplitInstructionsView(),
- 0,
- config.getInterpolator(
- ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
- LINEAR
- )
- );
- }
+ boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+ && !toState.overviewUi;
+ if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
+ setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
+ .createPlaceholderDismissAnim(mLauncher));
+ setter.setViewAlpha(
+ mRecentsView.getSplitInstructionsView(),
+ 0,
+ config.getInterpolator(
+ ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
+ LINEAR
+ )
+ );
}
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java b/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
deleted file mode 100644
index d8aa235..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import static android.os.IBinder.FLAG_ONEWAY;
-
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.function.BiConsumer;
-import java.util.function.Supplier;
-
-/**
- * A binder proxy transaction listener for tracking non-whitelisted binder calls.
- */
-public class DejankBinderTracker implements Binder.ProxyTransactListener {
- private static final String TAG = "DejankBinderTracker";
-
- private static final Object sLock = new Object();
- private static final HashSet<String> sWhitelistedFrameworkClasses = new HashSet<>();
- static {
- // Common IPCs that are ok to block the main thread.
- sWhitelistedFrameworkClasses.add("android.view.IWindowSession");
- sWhitelistedFrameworkClasses.add("android.os.IPowerManager");
- }
- private static boolean sTemporarilyIgnoreTracking = false;
-
- // Used by the client to limit binder tracking to specific regions
- private static boolean sTrackingAllowed = false;
-
- private BiConsumer<String, Integer> mUnexpectedTransactionCallback;
- private boolean mIsTracking = false;
-
- /**
- * Temporarily ignore blocking binder calls for the duration of this {@link Runnable}.
- */
- @MainThread
- public static void whitelistIpcs(Runnable runnable) {
- sTemporarilyIgnoreTracking = true;
- runnable.run();
- sTemporarilyIgnoreTracking = false;
- }
-
- /**
- * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
- */
- @MainThread
- public static <T> T whitelistIpcs(Supplier<T> supplier) {
- sTemporarilyIgnoreTracking = true;
- T value = supplier.get();
- sTemporarilyIgnoreTracking = false;
- return value;
- }
-
- /**
- * Enables binder tracking during a test.
- */
- @MainThread
- public static void allowBinderTrackingInTests() {
- sTrackingAllowed = true;
- }
-
- /**
- * Disables binder tracking during a test.
- */
- @MainThread
- public static void disallowBinderTrackingInTests() {
- sTrackingAllowed = false;
- }
-
- public DejankBinderTracker(BiConsumer<String, Integer> unexpectedTransactionCallback) {
- mUnexpectedTransactionCallback = unexpectedTransactionCallback;
- }
-
- @MainThread
- public void startTracking() {
- if (!Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
- && !Build.TYPE.toLowerCase(Locale.ROOT).equals("eng")) {
- Log.wtf(TAG, "Unexpected use of binder tracker in non-debug build", new Exception());
- return;
- }
- if (mIsTracking) {
- return;
- }
- mIsTracking = true;
- Binder.setProxyTransactListener(this);
- }
-
- @MainThread
- public void stopTracking() {
- if (!mIsTracking) {
- return;
- }
- mIsTracking = false;
- Binder.setProxyTransactListener(null);
- }
-
- // Override the hidden Binder#onTransactStarted method
- public synchronized Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
- if (!mIsTracking
- || !sTrackingAllowed
- || sTemporarilyIgnoreTracking
- || (flags & FLAG_ONEWAY) == FLAG_ONEWAY
- || !isMainThread()) {
- return null;
- }
-
- String descriptor;
- try {
- descriptor = binder.getInterfaceDescriptor();
- if (sWhitelistedFrameworkClasses.contains(descriptor)) {
- return null;
- }
- } catch (RemoteException e) {
- e.printStackTrace();
- descriptor = binder.getClass().getSimpleName();
- }
-
- mUnexpectedTransactionCallback.accept(descriptor, transactionCode);
- return null;
- }
-
- @Override
- public Object onTransactStarted(IBinder binder, int transactionCode) {
- // Do nothing
- return null;
- }
-
- @Override
- public void onTransactEnded(Object session) {
- // Do nothing
- }
-
- public static boolean isMainThread() {
- return Thread.currentThread() == Looper.getMainLooper().getThread();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index a8b7698..2064fe2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
@@ -189,7 +189,7 @@
} else {
float[] hctPlateColor = new float[3];
ColorUtils.colorToM3HCT(mDotParams.appColor, hctPlateColor);
- newPlateColor = ColorUtils.M3HCTtoColor(hctPlateColor[0], 36, 85);
+ newPlateColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
}
if (!animate) {
@@ -260,8 +260,8 @@
Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
};
- keyframes[1].setInterpolator(ACCEL_DEACCEL);
- keyframes[2].setInterpolator(ACCEL_DEACCEL);
+ keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
+ keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
@@ -337,7 +337,6 @@
if (getTag() instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
isBadged = !Process.myUserHandle().equals(info.user)
- || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index eb9332d..5a46b8d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -20,23 +20,20 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
-import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
@@ -60,8 +57,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
@@ -71,20 +66,20 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.media.permission.SafeCloseable;
import android.os.Build;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
-import android.view.RemoteAnimationTarget;
import android.view.View;
+import android.widget.AnalogClock;
+import android.widget.TextClock;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
@@ -111,7 +106,6 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
@@ -119,6 +113,7 @@
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
import com.android.launcher3.statehandlers.DepthController;
@@ -160,12 +155,10 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.AsyncClockEventDelegate;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
-import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
@@ -175,16 +168,14 @@
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
-import com.android.systemui.unfold.UnfoldSharedComponent;
import com.android.systemui.unfold.UnfoldTransitionFactory;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
-import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
-import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import java.io.FileDescriptor;
@@ -194,13 +185,17 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class QuickstepLauncher extends Launcher {
+ private static final boolean TRACE_LAYOUTS =
+ SystemProperties.getBoolean("persist.debug.trace_layouts", false);
+ private static final String TRACE_RELAYOUT_CLASS =
+ SystemProperties.get("persist.debug.trace_request_layout_class", null);
- public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
+ private static final String TAG = "QuickstepLauncher";
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
@@ -213,11 +208,8 @@
private QuickstepTransitionManager mAppTransitionManager;
private OverviewActionsView mActionsView;
private TISBindHelper mTISBindHelper;
- private @Nullable TaskbarManager mTaskbarManager;
- private @Nullable OverviewCommandHelper mOverviewCommandHelper;
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
// Will be updated when dragging from taskbar.
- private @Nullable DragOptions mNextWorkspaceDragOptions = null;
private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
@@ -225,6 +217,8 @@
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
+ private AsyncClockEventDelegate mAsyncClockEventDelegate;
+
/**
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
* recover. In all other cases this will remain null.
@@ -260,6 +254,10 @@
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
mDesktopVisibilityController = new DesktopVisibilityController(this);
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ mDesktopVisibilityController.registerSystemUiListener();
+ mSplitSelectStateController.initSplitFromDesktopController(this);
+ }
mHotseatPredictionController = new HotseatPredictionController(this);
mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
@@ -281,7 +279,6 @@
if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION
- || info.itemType == ITEM_TYPE_SHORTCUT
|| info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
int count = mAllAppsPredictions.items.size();
for (int i = 0; i < count; i++) {
@@ -351,9 +348,7 @@
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
RunnableList result = super.startActivitySafely(v, intent, item);
if (result == null) {
- if (getTaskbarUIController() == null) {
- mHotseatPredictionController.setPauseUIUpdate(false);
- }
+ mHotseatPredictionController.setPauseUIUpdate(false);
} else {
result.add(() -> mHotseatPredictionController.setPauseUIUpdate(false));
}
@@ -408,8 +403,7 @@
}
private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
-
- if (!ENABLE_SPLIT_FROM_WORKSPACE.get() || !mDeviceProfile.isTablet) {
+ if (!mDeviceProfile.isTablet || mSplitSelectStateController.isSplitSelectActive()) {
return Collections.emptyList();
}
RecentsView recentsView = getOverviewPanel();
@@ -437,12 +431,8 @@
boolean visible = (state == NORMAL || state == OVERVIEW)
&& (willUserBeActive || isUserActive())
&& !profile.isVerticalBarLayout();
- if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
- SystemUiProxy.INSTANCE.get(this)
- .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
- } else {
- SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
- }
+ SystemUiProxy.INSTANCE.get(this)
+ .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
}
if (state == NORMAL && !inTransition) {
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
@@ -451,6 +441,7 @@
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
+ Log.d(TAG, "Bind extra container items");
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
PredictionRowView<?> predictionRowView =
@@ -458,6 +449,7 @@
PredictionRowView.class);
predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ Log.d(TAG, "Bind extra container item is hotseat prediction");
mHotseatPredictionController.setPredictedItems(item);
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
getPopupDataProvider().setRecommendedWidgets(item.items);
@@ -474,22 +466,28 @@
public void onDestroy() {
mAppTransitionManager.onActivityDestroyed();
if (mUnfoldTransitionProgressProvider != null) {
- if (FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI.get()) {
- SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
- }
-
+ SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
mUnfoldTransitionProgressProvider.destroy();
}
mTISBindHelper.onDestroy();
- if (mTaskbarManager != null) {
- mTaskbarManager.clearActivity(this);
- }
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onDestroy();
}
+ if (mDesktopVisibilityController != null) {
+ mDesktopVisibilityController.unregisterSystemUiListener();
+ }
+
+ if (mSplitSelectStateController != null) {
+ mSplitSelectStateController.onDestroy();
+ }
+
+ if (mAsyncClockEventDelegate != null) {
+ mAsyncClockEventDelegate.onDestroy();
+ }
+
super.onDestroy();
mHotseatPredictionController.destroy();
mSplitWithKeyboardShortcutController.onDestroy();
@@ -523,7 +521,7 @@
}
case QUICK_SWITCH_STATE_ORDINAL: {
RecentsView rv = getOverviewPanel();
- TaskView tasktolaunch = rv.getTaskViewAt(0);
+ TaskView tasktolaunch = rv.getCurrentPageTaskView();
if (tasktolaunch != null) {
tasktolaunch.launchTask(success -> {
if (!success) {
@@ -547,11 +545,14 @@
ArrayList<TouchController> list = new ArrayList<>();
list.add(getDragController());
+ Consumer<AnimatorSet> splitAnimator = animatorSet ->
+ animatorSet.play(mSplitSelectStateController.getSplitAnimationController()
+ .createPlaceholderDismissAnim(this));
switch (mode) {
case NO_BUTTON:
list.add(new NoButtonQuickSwitchTouchController(this));
- list.add(new NavBarToHomeTouchController(this));
- list.add(new NoButtonNavbarToOverviewTouchController(this));
+ list.add(new NavBarToHomeTouchController(this, splitAnimator));
+ list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
break;
case TWO_BUTTONS:
list.add(new TwoButtonNavbarTouchController(this));
@@ -561,8 +562,14 @@
list.add(new PortraitStatesTouchController(this));
break;
case THREE_BUTTONS:
+ list.add(new NoButtonQuickSwitchTouchController(this));
+ list.add(new NavBarToHomeTouchController(this, splitAnimator));
+ list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
+ list.add(new PortraitStatesTouchController(this));
+ break;
default:
list.add(new PortraitStatesTouchController(this));
+ break;
}
if (!getDeviceProfile().isMultiWindowMode) {
@@ -603,6 +610,8 @@
mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
}
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
+ View.setTraceLayoutSteps(TRACE_LAYOUTS);
+ View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
}
@Override
@@ -610,12 +619,14 @@
RecentsView recentsView = getOverviewPanel();
// Check if there is already an instance of this app running, if so, initiate the split
// using that.
- mSplitSelectStateController.findLastActiveTaskAndRunCallback(
- splitSelectSource.itemInfo.getComponentKey(),
- foundTask -> {
- splitSelectSource.alreadyRunningTaskId = foundTask == null
- ? INVALID_TASK_ID
- : foundTask.key.id;
+ mSplitSelectStateController.findLastActiveTasksAndRunCallback(
+ Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
+ foundTasks -> {
+ @Nullable Task foundTask = foundTasks.get(0);
+ boolean taskWasFound = foundTask != null;
+ splitSelectSource.alreadyRunningTaskId = taskWasFound
+ ? foundTask.key.id
+ : INVALID_TASK_ID;
if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
startSplitToHome(splitSelectSource);
} else {
@@ -655,9 +666,13 @@
@Override
public void onAnimationCancel(Animator animation) {
getDragLayer().removeView(floatingTaskView);
+ mSplitSelectStateController.getSplitAnimationController()
+ .removeSplitInstructionsView(QuickstepLauncher.this);
mSplitSelectStateController.resetState();
}
});
+ anim.add(mSplitSelectStateController.getSplitAnimationController()
+ .getShowSplitInstructionsAnim(this).buildAnim());
anim.buildAnim().start();
}
@@ -683,9 +698,9 @@
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
-
- if (mOverviewCommandHelper != null) {
- mOverviewCommandHelper.clearPendingCommands();
+ OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+ if (overviewCommandHelper != null) {
+ overviewCommandHelper.clearPendingCommands();
}
}
@@ -808,8 +823,9 @@
}
private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
- if (mTaskbarManager == null
- || mTaskbarManager.getCurrentActivityContext() == null
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager == null
+ || taskbarManager.getCurrentActivityContext() == null
|| mTaskbarUIController == null) {
return;
}
@@ -853,7 +869,7 @@
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
DesktopVisibilityController controller = mDesktopVisibilityController;
if (controller != null && controller.areFreeformTasksVisible()
- && !controller.isGestureInProgress()) {
+ && !controller.isRecentsGestureInProgress()) {
// Return early to skip setting activity to appear as resumed
// TODO(b/255649902): shouldn't be needed when we have a separate launcher state
// for desktop that we can use to control other parts of launcher
@@ -881,11 +897,10 @@
}
private void onTISConnected(TISBinder binder) {
- mTaskbarManager = binder.getTaskbarManager();
- if (mTaskbarManager != null) {
- mTaskbarManager.setActivity(this);
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager != null) {
+ taskbarManager.setActivity(this);
}
- mOverviewCommandHelper = binder.getOverviewCommandHelper();
}
@Override
@@ -896,43 +911,10 @@
private void initUnfoldTransitionProgressProvider() {
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
if (config.isEnabled()) {
- if (RECEIVE_UNFOLD_EVENTS_FROM_SYSUI.get()) {
- initRemotelyCalculatedUnfoldAnimation(config);
- } else {
- initLocallyCalculatedUnfoldAnimation(config);
- }
-
+ initRemotelyCalculatedUnfoldAnimation(config);
}
}
- /** Registers hinge angle listener and calculates the animation progress in this process. */
- private void initLocallyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
- UnfoldSharedComponent unfoldComponent =
- UnfoldTransitionFactory.createUnfoldSharedComponent(
- /* context= */ this,
- config,
- ProxyScreenStatusProvider.INSTANCE,
- new DeviceStateManagerFoldProvider(
- getSystemService(DeviceStateManager.class), /* context= */ this),
- new ActivityManagerActivityTypeProvider(
- getSystemService(ActivityManager.class)),
- getSystemService(SensorManager.class),
- getMainThreadHandler(),
- getMainExecutor(),
- /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
- /* tracingTagPrefix= */ "launcher",
- getSystemService(DisplayManager.class)
- );
-
- mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
- .orElseThrow(() -> new IllegalStateException(
- "Trying to create UnfoldTransitionProgressProvider when the "
- + "transition is disabled"));
-
- initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
- unfoldComponent.getRotationChangeProvider());
- }
-
/** Receives animation progress from sysui process. */
private void initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
RemoteUnfoldSharedComponent unfoldComponent =
@@ -982,6 +964,13 @@
return mSplitToWorkspaceController;
}
+ @Override
+ protected void handleSplitAnimationGoingToHome() {
+ super.handleSplitAnimationGoingToHome();
+ mSplitSelectStateController.getSplitAnimationController()
+ .playPlaceholderDismissAnim(this);
+ }
+
public <T extends OverviewActionsView> T getActionsView() {
return (T) mActionsView;
}
@@ -1018,41 +1007,6 @@
}
@Override
- public DragOptions getDefaultWorkspaceDragOptions() {
- if (mNextWorkspaceDragOptions != null) {
- DragOptions options = mNextWorkspaceDragOptions;
- mNextWorkspaceDragOptions = null;
- return options;
- }
- return super.getDefaultWorkspaceDragOptions();
- }
-
- public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
- mNextWorkspaceDragOptions = dragOptions;
- }
-
- @Override
- public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
- QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
- appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
- @Override
- public AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
- RemoteAnimationTarget[] wallpaperTargets) {
-
- // On the first call clear the reference.
- signal.cancel();
-
- ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
- fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
- wallpaperTargets));
- AnimatorSet anim = new AnimatorSet();
- anim.play(fadeAnimation);
- return anim;
- }
- }, signal);
- }
-
- @Override
public float[] getNormalOverviewScaleAndOffset() {
return DisplayController.getNavigationMode(this).hasGestures
? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
@@ -1102,6 +1056,8 @@
activityOptions.options.setLaunchDisplayId(
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
: Display.DEFAULT_DISPLAY);
+ activityOptions.options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
addLaunchCookie(item, activityOptions.options);
return activityOptions;
}
@@ -1114,6 +1070,8 @@
Executors.MAIN_EXECUTOR.getHandler(), null,
elapsedRealTime -> callbacks.executeAllAndDestroy());
options.setSplashScreenStyle(splashScreenStyle);
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
return new ActivityOptionsWrapper(options, callbacks);
}
@@ -1161,7 +1119,6 @@
}
switch (info.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_APPWIDGET:
// Fall through and continue if it's an app, shortcut, or widget
@@ -1270,8 +1227,9 @@
Trace.instantForTrack(TRACE_TAG_APP, "QuickstepLauncher#DeviceProfileChanged",
getDeviceProfile().toSmallString());
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
- if (mTaskbarManager != null) {
- mTaskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager != null) {
+ taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
}
}
@@ -1293,7 +1251,7 @@
groupTask.task1.key.id,
groupTask.task2.key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
- /* callback= */ success -> {},
+ /* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ true,
groupTask.mSplitBounds == null
? DEFAULT_SPLIT_RATIO
@@ -1302,6 +1260,18 @@
: groupTask.mSplitBounds.leftTaskPercent);
}
+ /**
+ * Launches two apps as an app pair.
+ */
+ public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
+ mSplitSelectStateController.getAppPairsController().launchAppPair(app1, app2);
+ }
+
+ public boolean canStartHomeSafely() {
+ OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+ return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
+ }
+
private static final class LauncherTaskViewController extends
TaskViewTouchController<Launcher> {
@@ -1341,5 +1311,31 @@
if (mAppTransitionManager != null) {
mAppTransitionManager.dump(prefix + "\t" + RING_APPEAR_ANIMATION_PREFIX, writer);
}
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.dump(prefix, writer);
+ }
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ switch (name) {
+ case "TextClock", "android.widget.TextClock" -> {
+ TextClock tc = new TextClock(context, attrs);
+ if (mAsyncClockEventDelegate == null) {
+ mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
+ }
+ tc.setClockEventDelegate(mAsyncClockEventDelegate);
+ return tc;
+ }
+ case "AnalogClock", "android.widget.AnalogClock" -> {
+ AnalogClock ac = new AnalogClock(context, attrs);
+ if (mAsyncClockEventDelegate == null) {
+ mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
+ }
+ ac.setClockEventDelegate(mAsyncClockEventDelegate);
+ return ac;
+ }
+ }
+ return super.onCreateView(parent, name, context, attrs);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index 39543b0..f7bef03 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -243,6 +243,7 @@
} else {
widgetView = new LauncherAppWidgetHostView(context);
}
+ widgetView.setIsWidgetCachingDisabled(true);
widgetView.setInteractionHandler(mInteractionHandler);
widgetView.setAppWidget(appWidgetId, appWidget);
mViews.put(appWidgetId, widgetView);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index f16b43d..23e922c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,10 +15,10 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
index b901a87..a76eb43 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
@@ -23,6 +23,7 @@
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -59,6 +60,7 @@
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreference;
import com.android.launcher3.LauncherPrefs;
@@ -106,6 +108,9 @@
loadPluginPrefs();
maybeAddSandboxCategory();
addOnboardingPrefsCatergory();
+ if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
+ addAllAppsFromOverviewCatergory();
+ }
if (getActivity() != null) {
getActivity().setTitle("Developer Options");
@@ -393,6 +398,33 @@
}
}
+ private void addAllAppsFromOverviewCatergory() {
+ PreferenceCategory category = newCategory("All Apps from Overview Config");
+
+ SeekBarPreference thresholdPref = new SeekBarPreference(getContext());
+ thresholdPref.setTitle("Threshold to open All Apps from Overview");
+ thresholdPref.setSingleLineTitle(false);
+
+ // These values are 100x swipe up shift value (100 = where overview sits).
+ thresholdPref.setMax(500);
+ thresholdPref.setMin(105);
+ thresholdPref.setUpdatesContinuously(true);
+ thresholdPref.setIconSpaceReserved(false);
+ // Don't directly save to shared prefs, use LauncherPrefs instead.
+ thresholdPref.setPersistent(false);
+ thresholdPref.setOnPreferenceChangeListener((preference, newValue) -> {
+ LauncherPrefs.get(getContext()).put(ALL_APPS_OVERVIEW_THRESHOLD, newValue);
+ preference.setSummary(String.valueOf((int) newValue / 100f));
+ return true;
+ });
+ int value = LauncherPrefs.get(getContext()).get(ALL_APPS_OVERVIEW_THRESHOLD);
+ thresholdPref.setValue(value);
+ // For some reason the initial value is not triggering the summary update, so call manually.
+ thresholdPref.getOnPreferenceChangeListener().onPreferenceChange(thresholdPref, value);
+
+ category.addPreference(thresholdPref);
+ }
+
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "")
.replace("com.android.launcher3.action.PLUGIN_", "");
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 2a42175..ed0a0d5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -15,14 +15,16 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import android.content.Context;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -91,7 +93,7 @@
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
PageAlphaProvider superPageAlphaProvider = super.getWorkspacePageAlphaProvider(launcher);
- return new PageAlphaProvider(DEACCEL_2) {
+ return new PageAlphaProvider(DECELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
return launcher.getDeviceProfile().isTablet
@@ -103,14 +105,52 @@
@Override
public int getVisibleElements(Launcher launcher) {
- // Don't add HOTSEAT_ICONS for non-tablets in ALL_APPS state.
- return launcher.getDeviceProfile().isTablet ? ALL_APPS_CONTENT | HOTSEAT_ICONS
- : ALL_APPS_CONTENT;
+ int elements = ALL_APPS_CONTENT | FLOATING_SEARCH_BAR;
+ // Only add HOTSEAT_ICONS for tablets in ALL_APPS state.
+ if (launcher.getDeviceProfile().isTablet) {
+ elements |= HOTSEAT_ICONS;
+ }
+ return elements;
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
+ return 0;
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
+ }
+
+ @Override
+ public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.isPhone && !dp.isLandscape;
}
@Override
public LauncherState getHistoryForState(LauncherState previousState) {
- return previousState == OVERVIEW ? OVERVIEW : NORMAL;
+ return previousState == BACKGROUND_APP ? QUICK_SWITCH_FROM_HOME
+ : previousState == OVERVIEW ? OVERVIEW : NORMAL;
+ }
+
+ @Override
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ if (!FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
+ return super.getOverviewScaleAndOffset(launcher);
+ }
+ // This handles the case of returning to the previous app from Overview -> All Apps gesture.
+ // This is the start scale/offset of overview that will be used for that transition.
+ // TODO (b/283336332): Translate in Y direction (ideally with overview resistance).
+ return new float[] {0.5f /* scale */, NO_OFFSET};
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 214679a..396d0ab 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import android.content.Context;
@@ -97,7 +97,7 @@
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
- return new PageAlphaProvider(DEACCEL_2) {
+ return new PageAlphaProvider(DECELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
return 0;
@@ -107,7 +107,32 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+ int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+ DeviceProfile dp = launcher.getDeviceProfile();
+ boolean showFloatingSearch;
+ if (dp.isPhone) {
+ // Only show search in phone overview in portrait mode.
+ showFloatingSearch = !dp.isLandscape;
+ } else {
+ // Only show search in tablet overview if taskbar is not visible.
+ showFloatingSearch = !dp.isTaskbarPresent || isTaskbarStashed(launcher);
+ }
+ if (showFloatingSearch) {
+ elements |= FLOATING_SEARCH_BAR;
+ }
+ return elements;
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
+ return areElementsVisible(launcher, FLOATING_SEARCH_BAR) ? 0
+ : super.getFloatingSearchBarRestingMarginBottom(launcher);
+ }
+
+ @Override
+ public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.isPhone && !dp.isLandscape;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index a8d7538..6651c73 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -17,6 +17,20 @@
import static android.view.View.VISIBLE;
+import static com.android.app.animation.Interpolators.ACCELERATE;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.DECELERATE_1_7;
+import static com.android.app.animation.Interpolators.DECELERATE_3;
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.OVERSHOOT_0_75;
+import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
@@ -25,20 +39,6 @@
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
@@ -55,12 +55,14 @@
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import android.animation.ValueAnimator;
+import android.util.Log;
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
@@ -94,7 +96,8 @@
@Override
public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
StateAnimationConfig config) {
-
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "creating animation fromState: "
+ + fromState + " toState: " + toState);
RecentsView overview = mActivity.getOverviewPanel();
if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
overview.switchToScreenshot(() ->
@@ -112,8 +115,8 @@
fromState == OVERVIEW_SPLIT_SELECT
? clampToProgress(LINEAR, 0.33f, 1)
: LINEAR);
- config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
- config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATE);
+ config.setInterpolator(ANIM_WORKSPACE_FADE, ACCELERATE);
if (DisplayController.getNavigationMode(mActivity).hasGestures
&& overview.getTaskViewCount() > 0) {
@@ -139,9 +142,9 @@
}
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
} else {
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
- config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
- config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCELERATE_DECELERATE);
+ config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCELERATE, 0, 0.9f));
+ config.setInterpolator(ANIM_OVERVIEW_FADE, DECELERATE_1_7);
}
Workspace<?> workspace = mActivity.getWorkspace();
@@ -167,8 +170,8 @@
|| fromState == HINT_STATE_TWO_BUTTON) && toState == OVERVIEW) {
if (DisplayController.getNavigationMode(mActivity).hasGestures) {
config.setInterpolator(ANIM_WORKSPACE_SCALE,
- fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
- config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+ fromState == NORMAL ? ACCELERATE : OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE);
// Scrolling in tasks, so show straight away
if (overview.getTaskViewCount() > 0) {
@@ -196,7 +199,7 @@
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_2);
} else if (fromState == HINT_STATE && toState == NORMAL) {
- config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
+ config.setInterpolator(ANIM_DEPTH, DECELERATE_3);
if (mHintToNormalDuration == -1) {
ValueAnimator va = getWorkspaceSpringScaleAnimator(mActivity,
mActivity.getWorkspace(),
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 8cbd6e8..b266bcd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,19 +15,22 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.app.animation.Interpolators.DECELERATE_3;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
@@ -41,24 +44,29 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
+import java.util.function.Consumer;
+
/**
* Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
*/
public class NavBarToHomeTouchController implements TouchController,
SingleAxisSwipeDetector.Listener {
- private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
+ private static final Interpolator PULLBACK_INTERPOLATOR = DECELERATE_3;
// The min amount of overview scrim we keep during the transition.
private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
private final Launcher mLauncher;
+ private final Consumer<AnimatorSet> mCancelSplitRunnable;
private final SingleAxisSwipeDetector mSwipeDetector;
private final float mPullbackDistance;
@@ -67,8 +75,14 @@
private LauncherState mEndState = NORMAL;
private AnimatorPlaybackController mCurrentAnimation;
- public NavBarToHomeTouchController(Launcher launcher) {
+ /**
+ * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
+ * Animation should be added to the provided AnimatorSet
+ */
+ public NavBarToHomeTouchController(Launcher launcher,
+ Consumer<AnimatorSet> cancelSplitRunnable) {
mLauncher = launcher;
+ mCancelSplitRunnable = cancelSplitRunnable;
mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
SingleAxisSwipeDetector.VERTICAL);
mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
@@ -95,6 +109,10 @@
}
private boolean canInterceptTouch(MotionEvent ev) {
+ if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
+ == THREE_BUTTONS) {
+ return false;
+ }
boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
if (!cameFromNavBar) {
return false;
@@ -176,7 +194,10 @@
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
if (mStartState.overviewUi) {
- new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
+ new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState),
+ FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+ ? mCancelSplitRunnable
+ : null)
.animateWithVelocity(velocity);
} else {
mLauncher.getStateManager().goToState(mEndState, true,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index b7bafd8..ca598c8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -16,19 +16,22 @@
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
@@ -43,6 +46,7 @@
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -50,6 +54,8 @@
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
+import java.util.function.Consumer;
+
/**
* Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
* the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
@@ -64,6 +70,7 @@
private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
private final VibratorWrapper mVibratorWrapper;
+ private final Consumer<AnimatorSet> mCancelSplitRunnable;
private final RecentsView mRecentsView;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
@@ -79,16 +86,26 @@
// Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
private ObjectAnimator mNormalToHintOverviewScrimAnimator;
- public NoButtonNavbarToOverviewTouchController(Launcher l) {
+ /**
+ * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
+ * Animation should be added to the provided AnimatorSet
+ */
+ public NoButtonNavbarToOverviewTouchController(Launcher l,
+ Consumer<AnimatorSet> cancelSplitRunnable) {
super(l);
mRecentsView = l.getOverviewPanel();
mMotionPauseDetector = new MotionPauseDetector(l);
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
+ mCancelSplitRunnable = cancelSplitRunnable;
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
+ if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
+ == THREE_BUTTONS) {
+ return false;
+ }
mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
.getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
@@ -102,9 +119,6 @@
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
if (fromState == NORMAL && mDidTouchStartInNavBar) {
return HINT_STATE;
- } else if (fromState == OVERVIEW && isDragTowardPositive) {
- // Don't allow swiping up to all apps.
- return OVERVIEW;
}
return super.getTargetState(fromState, isDragTowardPositive);
}
@@ -190,6 +204,9 @@
// state, but since the hint state tracks the entire screen without a clear endpoint, we
// need to manually set the duration to a reasonable value.
animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
+ AnimatorSet animatorSet = new AnimatorSet();
+ mCancelSplitRunnable.accept(animatorSet);
+ animatorSet.start();
}
if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
((mFromState == NORMAL && mToState == ALL_APPS)
@@ -261,7 +278,7 @@
private void goToOverviewOrHomeOnDragEnd(float velocity) {
boolean goToHomeInsteadOfOverview = !mMotionPauseDetector.isPaused();
if (goToHomeInsteadOfOverview) {
- new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL))
+ new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL), null)
.animateWithVelocity(velocity);
}
if (mReachedOverview) {
@@ -273,7 +290,7 @@
mRecentsView.animate()
.translationX(0)
.translationY(0)
- .setInterpolator(ACCEL_DEACCEL)
+ .setInterpolator(ACCELERATE_DECELERATE)
.setDuration(duration)
.withEndAction(goToHomeInsteadOfOverview
? null
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 80f5558..6f421eb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,21 +16,21 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
+import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
+import static com.android.app.animation.Interpolators.DECELERATE_3;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
@@ -46,6 +46,7 @@
import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
@@ -74,6 +75,7 @@
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
@@ -84,6 +86,7 @@
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
/**
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -93,8 +96,8 @@
BothAxesSwipeDetector.Listener {
private static final float Y_ANIM_MIN_PROGRESS = 0.25f;
- private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_3;
- private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
+ private static final Interpolator FADE_OUT_INTERPOLATOR = DECELERATE_3;
+ private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCELERATE_0_75;
private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
@@ -110,7 +113,6 @@
newCancelListener(this::clearState);
private boolean mNoIntercept;
- private Boolean mIsTrackpadFourFingerSwipe;
private LauncherState mStartState;
private boolean mIsHomeScreenVisible = true;
@@ -136,9 +138,7 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getActionMasked();
- if (action == ACTION_DOWN) {
- mIsTrackpadFourFingerSwipe = null;
+ if (ev.getActionMasked() == ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
@@ -147,13 +147,6 @@
// Only detect horizontal swipe for intercept, then we will allow swipe up as well.
mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
false /* ignoreSlopWhenSettling */);
- } else if (isTrackpadMultiFingerSwipe(ev) && mIsTrackpadFourFingerSwipe == null
- && action == ACTION_MOVE) {
- mIsTrackpadFourFingerSwipe = isTrackpadFourFingerSwipe(ev);
- mNoIntercept = !mIsTrackpadFourFingerSwipe;
- if (mNoIntercept) {
- return false;
- }
}
if (mNoIntercept) {
@@ -170,6 +163,10 @@
}
private boolean canInterceptTouch(MotionEvent ev) {
+ if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
+ == THREE_BUTTONS) {
+ return false;
+ }
if (!mLauncher.isInState(LauncherState.NORMAL)) {
return false;
}
@@ -184,6 +181,9 @@
// TODO(b/268075592): add support for quickswitch to/from desktop
return false;
}
+ if (isTrackpadMultiFingerSwipe(ev)) {
+ return isTrackpadFourFingerSwipe(ev);
+ }
return true;
}
@@ -191,6 +191,9 @@
public void onDragStart(boolean start) {
mMotionPauseDetector.clear();
if (start) {
+ InteractionJankMonitorWrapper.begin(mRecentsView,
+ InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+
mStartState = mLauncher.getStateManager().getState();
mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
@@ -325,6 +328,7 @@
if (mMotionPauseDetector.isPaused() && noFling) {
// Going to Overview.
cancelAnimations();
+ InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
StateAnimationConfig config = new StateAnimationConfig();
config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
@@ -441,6 +445,8 @@
RecentsView.SCROLL_VIBRATION_PRIMITIVE,
RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
RecentsView.SCROLL_VIBRATION_FALLBACK);
+ } else {
+ InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
@@ -462,6 +468,11 @@
: targetState.ordinal > mStartState.ordinal
? LAUNCHER_UNKNOWN_SWIPEUP
: LAUNCHER_UNKNOWN_SWIPEDOWN));
+
+ if (targetState == QUICK_SWITCH_FROM_HOME) {
+ InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ }
+
mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 8368f9c..e30fe66 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -24,11 +24,12 @@
import android.view.MotionEvent;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.AllAppsSwipeController;
@@ -92,9 +93,9 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
if (fromState == ALL_APPS && !isDragTowardPositive) {
- return NORMAL;
- } else if (fromState == OVERVIEW) {
- return isDragTowardPositive ? OVERVIEW : NORMAL;
+ return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
+ ? mLauncher.getStateManager().getLastState()
+ : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
return ALL_APPS;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index f941b02..9a35bb2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -15,12 +15,12 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.app.animation.Interpolators.ACCELERATE_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
@@ -128,14 +128,14 @@
}
private void setupInterpolators(StateAnimationConfig stateAnimationConfig) {
- stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DECELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DECELERATE_2);
if (DisplayController.getNavigationMode(mLauncher) == NavigationMode.NO_BUTTON) {
// Overview lives to the left of workspace, so translate down later than over
- stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCELERATE_2);
stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
} else {
stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 395833f..26ab3d6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -76,7 +76,7 @@
private void dispatchTouchEvent(MotionEvent ev) {
if (mSystemUiProxy.isActive()) {
mLastAction = ev.getActionMasked();
- mSystemUiProxy.onStatusBarMotionEvent(ev);
+ mSystemUiProxy.onStatusBarTouchEvent(ev);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index eddc50c..3d94857 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -27,13 +27,13 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.PagedOrientationHandler;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 43db7c3..1ef4039 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -21,19 +21,19 @@
import static android.view.Surface.ROTATION_90;
import static android.widget.Toast.LENGTH_SHORT;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.launcher3.PagedView.INVALID_PAGE;
-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.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;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
-import static com.android.launcher3.uioverrides.QuickstepLauncher.ENABLE_PIP_KEEP_CLEAR_ALGORITHM;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -94,6 +94,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -101,14 +102,15 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarThresholdUtils;
import com.android.launcher3.taskbar.TaskbarUIController;
-import com.android.launcher3.tracing.InputConsumerProto;
-import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
@@ -122,7 +124,6 @@
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -133,6 +134,7 @@
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -149,6 +151,7 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.function.Consumer;
/**
@@ -163,9 +166,6 @@
private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
- /** Shift distance to transition to All Apps if ENABLE_ALL_APPS_FROM_OVERVIEW. */
- public static final float ALL_APPS_SHIFT_THRESHOLD = 2f;
-
protected final BaseActivityInterface<S, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
@@ -193,6 +193,7 @@
ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
mRecentsView = null;
mActivity = null;
+ mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
}
};
@@ -293,7 +294,8 @@
private boolean mContinuingLastGesture;
- private ThumbnailData mTaskSnapshot;
+ // Cache of recently-updated task snapshots, mapping task id to ThumbnailData
+ private HashMap<Integer, ThumbnailData> mTaskSnapshotCache = new HashMap<>();
// Used to control launcher components throughout the swipe gesture.
private AnimatorControllerWithResistance mLauncherTransitionController;
@@ -378,12 +380,12 @@
mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
mTaskbarAppWindowThreshold =
- res.getDimensionPixelSize(R.dimen.taskbar_app_window_threshold);
+ TaskbarThresholdUtils.getAppWindowThreshold(res, mDp);
boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen || mGestureState.isTrackpadGesture();
mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
? 0
- : res.getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold);
- mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold);
+ : TaskbarThresholdUtils.getHomeOverviewThreshold(res, mDp);
+ mTaskbarCatchUpThreshold = TaskbarThresholdUtils.getCatchUpThreshold(res, mDp);
}
@Nullable
@@ -516,20 +518,22 @@
// Set up a entire animation lifecycle callback to notify the current recents view when
// the animation is canceled
mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
- HashMap<Integer, ThumbnailData> snapshots =
- mGestureState.consumeRecentsAnimationCanceledSnapshot();
- if (snapshots != null) {
- mRecentsView.switchToScreenshot(snapshots, () -> {
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.cleanupScreenshot();
- } else if (mDeferredCleanupRecentsAnimationController != null) {
- mDeferredCleanupRecentsAnimationController.cleanupScreenshot();
- mDeferredCleanupRecentsAnimationController = null;
- }
- });
- mRecentsView.onRecentsAnimationComplete();
- }
- });
+ if (mRecentsView == null) return;
+
+ HashMap<Integer, ThumbnailData> snapshots =
+ mGestureState.consumeRecentsAnimationCanceledSnapshot();
+ if (snapshots != null) {
+ mRecentsView.switchToScreenshot(snapshots, () -> {
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.cleanupScreenshot();
+ } else if (mDeferredCleanupRecentsAnimationController != null) {
+ mDeferredCleanupRecentsAnimationController.cleanupScreenshot();
+ mDeferredCleanupRecentsAnimationController = null;
+ }
+ });
+ mRecentsView.onRecentsAnimationComplete();
+ }
+ });
setupRecentsViewUi();
mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
@@ -587,7 +591,7 @@
if (mWasLauncherAlreadyVisible) {
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
} else {
- Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
+ SafeCloseable traceToken = TraceHelper.INSTANCE.beginAsyncSection("WTS-init");
View dragLayer = activity.getDragLayer();
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
boolean mHandled = false;
@@ -599,7 +603,7 @@
}
mHandled = true;
- TraceHelper.INSTANCE.endSection(traceToken);
+ traceToken.close();
dragLayer.post(() ->
dragLayer.getViewTreeObserver().removeOnDrawListener(this));
if (activity != mActivity) {
@@ -657,11 +661,12 @@
protected void notifyGestureAnimationStartToRecents() {
Task[] runningTasks;
+ TopTaskTracker.CachedTaskInfo cachedTaskInfo = mGestureState.getRunningTask();
if (mIsSwipeForSplit) {
int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
- runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
+ runningTasks = cachedTaskInfo.getPlaceholderTasks(splitTaskIds);
} else {
- runningTasks = mGestureState.getRunningTask().getPlaceholderTasks();
+ runningTasks = cachedTaskInfo.getPlaceholderTasks();
}
// Safeguard against any null tasks being sent to recents view, happens when quickswitching
@@ -681,11 +686,10 @@
private void initializeLauncherAnimationController() {
buildAnimationController();
- Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
- TraceHelper.FLAG_IGNORE_BINDERS);
- LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
- (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
- TraceHelper.INSTANCE.endSection(traceToken);
+ try (SafeCloseable c = TraceHelper.INSTANCE.allowIpcs("logToggleRecents")) {
+ LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
+ (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+ }
// This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
// high-res thumbnail loader here once we are sure that we will end up in an overview state
@@ -731,11 +735,15 @@
* @param moveRunningTask whether to move running task to front when attaching
*/
private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) {
- if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
+ if ((!mDeviceState.isFullyGesturalNavMode() && !mGestureState.isTrackpadGesture())
+ || mRecentsView == null) {
return;
}
+ // looking at single target is fine here since either app of a split pair would
+ // have their "isInRecents" field set? (that's what this is used for below)
RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
- ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ ? mRecentsAnimationTargets
+ .findTask(mGestureState.getTopRunningTaskId())
: null;
final boolean recentsAttachedToAppWindow;
if (mIsInAllAppsRegion) {
@@ -810,6 +818,10 @@
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
maybeUpdateRecentsAttachedState(true);
+ if (mActivity != null) {
+ mActivity.getAppsView().getSearchUiManager().prepareToFocusEditText(mIsInAllAppsRegion);
+ }
+
// Draw active task below Launcher so that All Apps can appear over it.
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(isInAllAppsRegion));
@@ -873,7 +885,8 @@
@UiThread
@Override
public void onCurrentShiftUpdated() {
- setIsInAllAppsRegion(mCurrentShift.value >= ALL_APPS_SHIFT_THRESHOLD);
+ float threshold = LauncherPrefs.get(mContext).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
+ setIsInAllAppsRegion(mCurrentShift.value >= threshold);
updateSysUiFlags(mCurrentShift.value);
applyScrollAndTransform();
@@ -1148,6 +1161,14 @@
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify the SysUI to use fade-in animation when entering PiP
SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ // Notify the SysUI to stash desktop apps if they are visible
+ DesktopVisibilityController desktopVisibilityController =
+ mActivityInterface.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController.onHomeActionTriggered();
+ }
+ }
break;
case RECENTS:
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1178,7 +1199,7 @@
return false;
}
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
- targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
+ mGestureState.mLastStartedTaskIdPredicate);
if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
reset();
return true;
@@ -1324,11 +1345,11 @@
Interpolator interpolator;
S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
if (state.displayOverviewTasksAsGrid(mDp)) {
- interpolator = ACCEL_DEACCEL;
+ interpolator = ACCELERATE_DECELERATE;
} else if (endTarget == RECENTS) {
interpolator = OVERSHOOT_1_2;
} else {
- interpolator = DEACCEL;
+ interpolator = DECELERATE;
}
if (endTarget.isLauncher) {
@@ -1444,9 +1465,12 @@
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (task.taskId == mGestureState.getRunningTaskId()
- && task.configuration.windowConfiguration.getActivityType()
- != ACTIVITY_TYPE_HOME) {
+ boolean taskRunningAndNotHome = Arrays.stream(mGestureState
+ .getRunningTaskIds(true /*getMultipleTasks*/))
+ .anyMatch(taskId -> task.taskId == taskId
+ && task.configuration.windowConfiguration.getActivityType()
+ != ACTIVITY_TYPE_HOME);
+ if (taskRunningAndNotHome) {
// Since this is an edge case, just cancel and relaunch with default activity
// options (since we don't know if there's an associated app icon to launch from)
endRunningWindowAnim(true /* cancel */);
@@ -1488,8 +1512,12 @@
if (mGestureState.getEndTarget() == HOME) {
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
+ // Take first task ID, if there are multiple we don't have any special home
+ // animation so doesn't matter for splitscreen.. though the "allowEnterPip" might change
+ // depending on which task it is..
final RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
- ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ ? mRecentsAnimationTargets
+ .findTask(mGestureState.getTopRunningTaskId())
: null;
final ArrayList<IBinder> cookies = runningTaskTarget != null
? runningTaskTarget.taskInfo.launchCookies
@@ -1518,8 +1546,10 @@
// grab a screenshot before the PipContentOverlay gets parented on top of the task
UI_HELPER_EXECUTOR.execute(() -> {
- mTaskSnapshot = mRecentsAnimationController.screenshotTask(
- mGestureState.getRunningTaskId());
+ // Directly use top task, split to pip handled on shell side
+ final int taskId = mGestureState.getTopRunningTaskId();
+ mTaskSnapshotCache.put(taskId,
+ mRecentsAnimationController.screenshotTask(taskId));
});
// let SystemUi reparent the overlay leash as soon as possible
@@ -1725,11 +1755,6 @@
private Rect getKeepClearAreaForHotseat() {
Rect keepClearArea;
- if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
- // make the height equal to hotseatBarSizePx only
- keepClearArea = new Rect(0, 0, 0, mDp.hotseatBarSizePx);
- return keepClearArea;
- }
// the keep clear area in global screen coordinates, in pixels
if (mDp.isPhone) {
if (mDp.isSeascape()) {
@@ -1908,7 +1933,7 @@
mActivityInitListener.unregister();
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mActivityRestartListener);
- mTaskSnapshot = null;
+ mTaskSnapshotCache.clear();
}
private void invalidateHandler() {
@@ -1926,7 +1951,7 @@
mActivityInitListener.unregister();
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mActivityRestartListener);
- mTaskSnapshot = null;
+ mTaskSnapshotCache.clear();
}
private void invalidateHandlerWithLauncher() {
@@ -1981,37 +2006,39 @@
// If there are no targets, then we don't need to capture anything
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else {
- final int runningTaskId = mGestureState.getRunningTaskId();
boolean finishTransitionPosted = false;
+ // If we already have cached screenshot(s) from running tasks, skip update
+ boolean shouldUpdate = false;
+ int[] runningTaskIds = mGestureState.getRunningTaskIds(mIsSwipeForSplit);
+ for (int id : runningTaskIds) {
+ if (!mTaskSnapshotCache.containsKey(id)) {
+ shouldUpdate = true;
+ break;
+ }
+ }
+
if (mRecentsAnimationController != null) {
// Update the screenshot of the task
- if (mTaskSnapshot == null) {
+ if (shouldUpdate) {
UI_HELPER_EXECUTOR.execute(() -> {
if (mRecentsAnimationController == null) return;
- final ThumbnailData taskSnapshot =
- mRecentsAnimationController.screenshotTask(runningTaskId);
- // If split case, we should update all split tasks snapshot
- if (mIsSwipeForSplit) {
- int[] splitTaskIds = TopTaskTracker.INSTANCE.get(
- mContext).getRunningSplitTaskIds();
- for (int i = 0; i < splitTaskIds.length; i++) {
- // Skip running one because done above.
- if (splitTaskIds[i] == runningTaskId) continue;
-
- mRecentsAnimationController.screenshotTask(splitTaskIds[i]);
- }
+ for (int id : runningTaskIds) {
+ mTaskSnapshotCache.put(
+ id, mRecentsAnimationController.screenshotTask(id));
}
+
MAIN_EXECUTOR.execute(() -> {
- mTaskSnapshot = taskSnapshot;
- if (!updateThumbnail(runningTaskId, false /* refreshView */)) {
+ if (!updateThumbnail(false /* refreshView */)) {
setScreenshotCapturedState();
}
});
});
return;
}
- finishTransitionPosted = updateThumbnail(runningTaskId, false /* refreshView */);
+
+ finishTransitionPosted = updateThumbnail(false /* refreshView */);
}
+
if (!finishTransitionPosted) {
setScreenshotCapturedState();
}
@@ -2019,35 +2046,34 @@
}
// Returns whether finish transition was posted.
- private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
- boolean finishTransitionPosted = false;
- final TaskView taskView;
+ private boolean updateThumbnail(boolean refreshView) {
if (mGestureState.getEndTarget() == HOME
|| mGestureState.getEndTarget() == NEW_TASK
|| mGestureState.getEndTarget() == ALL_APPS
|| mRecentsView == null) {
// Capture the screenshot before finishing the transition to home or quickswitching to
// ensure it's taken in the correct orientation, but no need to update the thumbnail.
- taskView = null;
- } else {
- taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, refreshView);
+ return false;
}
- if (taskView != null && refreshView && !mCanceled) {
+
+ boolean finishTransitionPosted = false;
+ TaskView updatedTaskView = mRecentsView.updateThumbnail(mTaskSnapshotCache, refreshView);
+ if (updatedTaskView != null && refreshView && !mCanceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
- finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
+ finishTransitionPosted = ViewUtils.postFrameDrawn(updatedTaskView,
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
this::isCanceled);
}
+
return finishTransitionPosted;
}
private void setScreenshotCapturedState() {
// If we haven't posted a draw callback, set the state immediately.
- Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
- TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
+ TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
private void finishCurrentTransitionToRecents() {
@@ -2188,16 +2214,27 @@
if (!mCanceled) {
TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
if (nextTask != null) {
- Task.TaskKey nextTaskKey = nextTask.getTask().key;
- int taskId = nextTaskKey.id;
- mGestureState.updateLastStartedTaskId(taskId);
- boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
- .contains(taskId);
+ int[] taskIds = nextTask.getTaskIds();
+ StringBuilder nextTaskLog = new StringBuilder();
+ for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
+ if (c == null) {
+ continue;
+ }
+ nextTaskLog
+ .append("[id: ")
+ .append(c.getTask().key.id)
+ .append(", pkg: ")
+ .append(c.getTask().key.getPackageName())
+ .append("] | ");
+ }
+ mGestureState.updateLastStartedTaskIds(taskIds);
+ boolean hasTaskPreviouslyAppeared = Arrays.stream(taskIds).anyMatch(
+ taskId -> mGestureState.getPreviouslyAppearedTaskIds()
+ .contains(taskId));
if (!hasTaskPreviouslyAppeared) {
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
}
- ActiveGestureLog.INSTANCE.addLog("Launching task: id=" + taskId
- + " pkg=" + nextTaskKey.getPackageName());
+ ActiveGestureLog.INSTANCE.addLog("Launching task: " + nextTaskLog);
nextTask.launchTask(success -> {
resultCallback.accept(success);
if (success) {
@@ -2267,7 +2304,7 @@
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
if (mRecentsAnimationController != null) {
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
- targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
+ mGestureState.mLastStartedTaskIdPredicate);
if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
// This is a special case, if a task is started mid-gesture that wasn't a part of a
// previous quickswitch task launch, then cancel the animation back to the app
@@ -2280,8 +2317,7 @@
} else if (handleTaskAppeared(appearedTaskTargets)) {
Optional<RemoteAnimationTarget> taskTargetOptional =
Arrays.stream(appearedTaskTargets)
- .filter(targetCompat ->
- targetCompat.taskId == mGestureState.getLastStartedTaskId())
+ .filter(mGestureState.mLastStartedTaskIdPredicate)
.findFirst();
if (!taskTargetOptional.isPresent()) {
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
@@ -2350,11 +2386,16 @@
* resume if we finish the controller.
*/
protected int getLastAppearedTaskIndex() {
- return mRecentsView == null
- ? -1
- : mGestureState.getLastAppearedTaskId() != -1
- ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
- : mRecentsView.getRunningTaskIndex();
+ if (mRecentsView == null) {
+ return -1;
+ }
+
+ OptionalInt firstValidTaskId = Arrays.stream(mGestureState.getLastAppearedTaskIds())
+ .filter(i -> i != -1)
+ .findFirst();
+ return firstValidTaskId.isPresent()
+ ? mRecentsView.getTaskIndexForId(firstValidTaskId.getAsInt())
+ : mRecentsView.getRunningTaskIndex();
}
/**
@@ -2362,7 +2403,7 @@
* but before that task appeared.
*/
protected boolean hasStartedNewTask() {
- return mGestureState.getLastStartedTaskId() != -1;
+ return mGestureState.getLastStartedTaskIds()[0] != -1;
}
/**
@@ -2406,7 +2447,6 @@
taskViewSimulator.apply(remoteHandle.getTransformParams());
}
}
- ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
// Scaling of RecentsView during quick switch based on amount of recents scroll
@@ -2428,11 +2468,11 @@
if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
- 0, maxScaleProgress, ACCEL_DEACCEL);
+ 0, maxScaleProgress, ACCELERATE_DECELERATE);
} else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
scaleProgress = Utilities.mapToRange(scrollOffset,
(maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
- maxScaleProgress, 0, ACCEL_DEACCEL);
+ maxScaleProgress, 0, ACCELERATE_DECELERATE);
}
return scaleProgress;
@@ -2461,7 +2501,7 @@
// "Catch up" with the displacement at mTaskbarCatchUpThreshold.
if (displacement < mTaskbarCatchUpThreshold) {
return Utilities.mapToRange(displacement, mTaskbarAppWindowThreshold,
- mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCEL_DEACCEL);
+ mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCELERATE_DECELERATE);
}
return displacement;
@@ -2476,26 +2516,6 @@
mRecentsAnimationTargets.nonApps, shown, null /* animatorHandler */);
}
- /**
- * Used for winscope tracing, see launcher_trace.proto
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param inputConsumerProto The parent of this proto message.
- */
- public void writeToProto(InputConsumerProto.Builder inputConsumerProto) {
- SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder();
-
- mGestureState.writeToProto(swipeHandlerProto);
-
- swipeHandlerProto.setIsRecentsAttachedToAppWindow(
- mAnimationFactory.isRecentsAttachedToAppWindow());
- swipeHandlerProto.setScrollOffset(mRecentsView == null
- ? 0
- : mRecentsView.getScrollOffset());
- swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value);
-
- inputConsumerProto.setSwipeHandler(swipeHandlerProto);
- }
-
public interface Factory {
AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 60083c6..5a9d80d 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -15,11 +15,11 @@
*/
package com.android.quickstep;
+import static com.android.app.animation.Interpolators.ACCELERATE_2;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-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;
@@ -553,7 +553,7 @@
long animationDuration = animate ? RECENTS_ATTACH_DURATION : 0;
Animator fadeAnim = mActivity.getStateManager()
.createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
- fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
+ fadeAnim.setInterpolator(attached ? INSTANT : ACCELERATE_2);
fadeAnim.setDuration(animationDuration);
animatorSet.play(fadeAnim);
diff --git a/quickstep/src/com/android/quickstep/BinderTracker.java b/quickstep/src/com/android/quickstep/BinderTracker.java
new file mode 100644
index 0000000..a876cd8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/BinderTracker.java
@@ -0,0 +1,187 @@
+/**
+ * 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.quickstep;
+
+import static android.os.IBinder.FLAG_ONEWAY;
+
+import android.os.Binder;
+import android.os.Binder.ProxyTransactListener;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.TraceHelper;
+
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import kotlin.random.Random;
+
+/**
+ * A binder proxy transaction listener for tracking binder calls on main thread.
+ */
+public class BinderTracker {
+
+ private static final String TAG = "BinderTracker";
+
+ // Common IPCs that are ok to block the main thread.
+ private static final Set<String> sAllowedFrameworkClasses = Set.of(
+ "android.view.IWindowSession",
+ "android.os.IPowerManager",
+ "android.os.IServiceManager");
+
+ /**
+ * Starts tracking binder class and returns a {@link SafeCloseable} to end tracking
+ */
+ public static SafeCloseable startTracking(Consumer<BinderCallSite> callback) {
+ TraceHelper current = TraceHelper.INSTANCE;
+
+ TraceHelperExtension helper = new TraceHelperExtension(callback);
+ TraceHelper.INSTANCE = helper;
+ Binder.setProxyTransactListener(helper);
+
+ return () -> {
+ Binder.setProxyTransactListener(null);
+ TraceHelper.INSTANCE = current;
+ };
+ }
+
+ private static final LinkedList<String> mMainThreadTraceStack = new LinkedList<>();
+ private static final LinkedList<String> mMainThreadIgnoreIpcStack = new LinkedList<>();
+
+ private static class TraceHelperExtension extends TraceHelper implements ProxyTransactListener {
+
+ private final Consumer<BinderCallSite> mUnexpectedTransactionCallback;
+
+ TraceHelperExtension(Consumer<BinderCallSite> unexpectedTransactionCallback) {
+ mUnexpectedTransactionCallback = unexpectedTransactionCallback;
+ }
+
+ @Override
+ public void beginSection(String sectionName) {
+ if (isMainThread()) {
+ mMainThreadTraceStack.add(sectionName);
+ }
+ super.beginSection(sectionName);
+ }
+
+ @Override
+ public SafeCloseable beginAsyncSection(String sectionName) {
+ if (!isMainThread()) {
+ return super.beginAsyncSection(sectionName);
+ }
+
+ mMainThreadTraceStack.add(sectionName);
+ int cookie = Random.Default.nextInt();
+ Trace.beginAsyncSection(sectionName, cookie);
+ return () -> {
+ Trace.endAsyncSection(sectionName, cookie);
+ mMainThreadTraceStack.remove(sectionName);
+ };
+ }
+
+ @Override
+ public void endSection() {
+ super.endSection();
+ if (isMainThread()) {
+ mMainThreadTraceStack.pollLast();
+ }
+ }
+
+ @Override
+ public SafeCloseable allowIpcs(String rpcName) {
+ if (!isMainThread()) {
+ return super.allowIpcs(rpcName);
+ }
+
+ mMainThreadTraceStack.add(rpcName);
+ mMainThreadIgnoreIpcStack.add(rpcName);
+ int cookie = Random.Default.nextInt();
+ Trace.beginAsyncSection(rpcName, cookie);
+ return () -> {
+ Trace.endAsyncSection(rpcName, cookie);
+ mMainThreadTraceStack.remove(rpcName);
+ mMainThreadIgnoreIpcStack.remove(rpcName);
+ };
+ }
+
+ @Override
+ public Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
+ if (!isMainThread() || (flags & FLAG_ONEWAY) == FLAG_ONEWAY) {
+ return null;
+ }
+
+ String ipcBypass = mMainThreadIgnoreIpcStack.peekLast();
+ String descriptor;
+ try {
+ descriptor = binder.getInterfaceDescriptor();
+ if (sAllowedFrameworkClasses.contains(descriptor)) {
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error getting IPC descriptor", e);
+ descriptor = binder.getClass().getSimpleName();
+ }
+
+ if (ipcBypass == null) {
+ mUnexpectedTransactionCallback.accept(new BinderCallSite(
+ mMainThreadTraceStack.peekLast(), descriptor, transactionCode));
+ } else {
+ Log.d(TAG, "MainThread-IPC " + descriptor + " ignored due to " + ipcBypass);
+ }
+ return null;
+ }
+
+ @Override
+ public Object onTransactStarted(IBinder binder, int transactionCode) {
+ // Do nothing
+ return null;
+ }
+
+ @Override
+ public void onTransactEnded(Object session) {
+ // Do nothing
+ }
+ }
+
+ private static boolean isMainThread() {
+ return Thread.currentThread() == Looper.getMainLooper().getThread();
+ }
+
+ /**
+ * Information about a binder call
+ */
+ public static class BinderCallSite {
+
+ @Nullable
+ public final String activeTrace;
+ public final String descriptor;
+ public final int transactionCode;
+
+ BinderCallSite(String activeTrace, String descriptor, int transactionCode) {
+ this.activeTrace = activeTrace;
+ this.descriptor = descriptor;
+ this.transactionCode = transactionCode;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index eff53f3..c5a88bc 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -19,13 +19,13 @@
import static android.content.Intent.EXTRA_COMPONENT_NAME;
import static android.content.Intent.EXTRA_USER;
+import static com.android.app.animation.Interpolators.ACCELERATE;
import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT;
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
import static com.android.launcher3.GestureNavContract.EXTRA_ON_FINISH_CALLBACK;
import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import android.animation.ObjectAnimator;
@@ -296,7 +296,7 @@
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
PendingAnimation pa = new PendingAnimation(mDuration);
- pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCEL);
+ pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCELERATE);
return pa.createPlaybackController();
}
@@ -324,7 +324,7 @@
@Override
public void playAtomicAnimation(float velocity) {
ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
- alphaAnim.setDuration(mDuration).setInterpolator(ACCEL);
+ alphaAnim.setDuration(mDuration).setInterpolator(ACCELERATE);
alphaAnim.start();
if (mRunningOverHome) {
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 9d7ccb4..c2d8c62 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -15,8 +15,9 @@
*/
package com.android.quickstep;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
-import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -37,8 +38,6 @@
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.tracing.GestureStateProto;
-import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
@@ -46,10 +45,12 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Manages the state for an active system gesture, listens for events from the system and Launcher,
@@ -58,28 +59,37 @@
@TargetApi(Build.VERSION_CODES.R)
public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
+ final Predicate<RemoteAnimationTarget> mLastStartedTaskIdPredicate = new Predicate<>() {
+ @Override
+ public boolean test(RemoteAnimationTarget targetCompat) {
+ for (int taskId : mLastStartedTaskId) {
+ if (targetCompat.taskId == taskId) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
/**
* Defines the end targets of a gesture and the associated state.
*/
public enum GestureEndTarget {
- HOME(true, LAUNCHER_STATE_HOME, false, GestureStateProto.GestureEndTarget.HOME),
+ HOME(true, LAUNCHER_STATE_HOME, false),
- RECENTS(true, LAUNCHER_STATE_OVERVIEW, true, GestureStateProto.GestureEndTarget.RECENTS),
+ RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
- NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
- GestureStateProto.GestureEndTarget.NEW_TASK),
+ NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
- LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
- GestureStateProto.GestureEndTarget.LAST_TASK),
+ LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
- ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false, GestureStateProto.GestureEndTarget.ALL_APPS);
+ ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false);
- GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow,
- GestureStateProto.GestureEndTarget protoEndTarget) {
+ GestureEndTarget(boolean isLauncher, int containerType,
+ boolean recentsAttachedToAppWindow) {
this.isLauncher = isLauncher;
this.containerType = containerType;
this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
- this.protoEndTarget = protoEndTarget;
}
/** Whether the target is in the launcher activity. Implicitly, if the end target is going
@@ -89,8 +99,6 @@
public final int containerType;
/** Whether RecentsView should be attached to the window as we animate to this target */
public final boolean recentsAttachedToAppWindow;
- /** The GestureStateProto enum value, used for winscope tracing. See launcher_trace.proto */
- public final GestureStateProto.GestureEndTarget protoEndTarget;
}
private static final String TAG = "GestureState";
@@ -150,15 +158,10 @@
public enum TrackpadGestureType {
NONE,
- // Assigned before we know whether it's a 3-finger or 4-finger gesture.
- MULTI_FINGER,
THREE_FINGER,
FOUR_FINGER;
public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) {
- if (!isTrackpadMultiFingerSwipe(event)) {
- return TrackpadGestureType.NONE;
- }
if (isTrackpadThreeFingerSwipe(event)) {
return TrackpadGestureType.THREE_FINGER;
}
@@ -166,16 +169,16 @@
return TrackpadGestureType.FOUR_FINGER;
}
- return TrackpadGestureType.MULTI_FINGER;
+ return TrackpadGestureType.NONE;
}
}
private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
private CachedTaskInfo mRunningTask;
private GestureEndTarget mEndTarget;
- private RemoteAnimationTarget mLastAppearedTaskTarget;
+ private RemoteAnimationTarget[] mLastAppearedTaskTargets;
private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
- private int mLastStartedTaskId = -1;
+ private int[] mLastStartedTaskId = new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
private RecentsAnimationController mRecentsAnimationController;
private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots;
@@ -201,7 +204,7 @@
mGestureId = other.mGestureId;
mRunningTask = other.mRunningTask;
mEndTarget = other.mEndTarget;
- mLastAppearedTaskTarget = other.mLastAppearedTaskTarget;
+ mLastAppearedTaskTargets = other.mLastAppearedTaskTargets;
mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds;
mLastStartedTaskId = other.mLastStartedTaskId;
}
@@ -305,10 +308,29 @@
}
/**
- * @return the running task id for this gesture.
+ * @param getMultipleTasks Whether multiple tasks or not are to be returned (for split)
+ * @return the running task ids for this gesture.
*/
- public int getRunningTaskId() {
- return mRunningTask != null ? mRunningTask.getTaskId() : -1;
+ public int[] getRunningTaskIds(boolean getMultipleTasks) {
+ if (mRunningTask == null) {
+ return new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
+ } else {
+ int cachedTasksSize = mRunningTask.mAllCachedTasks.size();
+ int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1);
+ int[] runningTaskIds = new int[count];
+ for (int i = 0; i < count; i++) {
+ runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId;
+ }
+ return runningTaskIds;
+ }
+ }
+
+ /**
+ * @see #getRunningTaskIds(boolean)
+ * @return the single top-most running taskId for this gesture
+ */
+ public int getTopRunningTaskId() {
+ return getRunningTaskIds(false /*getMultipleTasks*/)[0];
}
/**
@@ -321,18 +343,26 @@
/**
* Updates the last task that appeared during this gesture.
*/
- public void updateLastAppearedTaskTarget(RemoteAnimationTarget lastAppearedTaskTarget) {
- mLastAppearedTaskTarget = lastAppearedTaskTarget;
- if (lastAppearedTaskTarget != null) {
- mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId);
+ public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) {
+ mLastAppearedTaskTargets = lastAppearedTaskTargets;
+ for (RemoteAnimationTarget target : lastAppearedTaskTargets) {
+ if (target == null) {
+ continue;
+ }
+ mPreviouslyAppearedTaskIds.add(target.taskId);
}
}
/**
* @return The id of the task that appeared during this gesture.
*/
- public int getLastAppearedTaskId() {
- return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1;
+ public int[] getLastAppearedTaskIds() {
+ if (mLastAppearedTaskTargets == null) {
+ return new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
+ } else {
+ return Arrays.stream(mLastAppearedTaskTargets)
+ .mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray();
+ }
}
public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) {
@@ -346,7 +376,7 @@
/**
* Updates the last task that we started via startActivityFromRecents() during this gesture.
*/
- public void updateLastStartedTaskId(int lastStartedTaskId) {
+ public void updateLastStartedTaskIds(int[] lastStartedTaskId) {
mLastStartedTaskId = lastStartedTaskId;
}
@@ -354,7 +384,7 @@
* @return The id of the task that was most recently started during this gesture, or -1 if
* no task has been started yet (i.e. we haven't settled on a new task).
*/
- public int getLastStartedTaskId() {
+ public int[] getLastStartedTaskIds() {
return mLastStartedTaskId;
}
@@ -490,21 +520,8 @@
pw.println(" gestureID=" + mGestureId);
pw.println(" runningTask=" + mRunningTask);
pw.println(" endTarget=" + mEndTarget);
- pw.println(" lastAppearedTaskTargetId=" + getLastAppearedTaskId());
- pw.println(" lastStartedTaskId=" + mLastStartedTaskId);
+ pw.println(" lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets));
+ pw.println(" lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
}
-
- /**
- * Used for winscope tracing, see launcher_trace.proto
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param swipeHandlerProto The parent of this proto message.
- */
- public void writeToProto(SwipeHandlerProto.Builder swipeHandlerProto) {
- GestureStateProto.Builder gestureStateProto = GestureStateProto.newBuilder();
- gestureStateProto.setEndTarget(mEndTarget == null
- ? GestureStateProto.GestureEndTarget.UNSET
- : mEndTarget.protoEndTarget);
- swipeHandlerProto.setGestureState(gestureStateProto);
- }
}
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 6b189cf..23def14 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -21,9 +21,6 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
-import com.android.launcher3.tracing.InputConsumerProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
-
@TargetApi(Build.VERSION_CODES.O)
public interface InputConsumer {
@@ -42,6 +39,7 @@
int TYPE_TASKBAR_STASH = 1 << 12;
int TYPE_STATUS_BAR = 1 << 13;
int TYPE_CURSOR_HOVER = 1 << 14;
+ int TYPE_NAV_HANDLE_LONG_PRESS = 1 << 15;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -59,6 +57,7 @@
"TYPE_TASKBAR_STASH", // 12
"TYPE_STATUS_BAR", // 13
"TYPE_CURSOR_HOVER", // 14
+ "TYPE_NAV_HANDLE_LONG_PRESS", // 15
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
@@ -127,21 +126,4 @@
}
return name.toString();
}
-
- /**
- * Used for winscope tracing, see launcher_trace.proto
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param serviceProto The parent of this proto message.
- */
- default void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
- InputConsumerProto.Builder inputConsumerProto = InputConsumerProto.newBuilder();
- inputConsumerProto.setName(getName());
- writeToProtoInternal(inputConsumerProto);
- serviceProto.setInputConsumer(inputConsumerProto);
- }
-
- /**
- * @see #writeToProto - allows subclasses to write additional info to the proto.
- */
- default void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {}
}
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
index 7638541..33a2366 100644
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -20,6 +20,9 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
@@ -49,4 +52,18 @@
ComponentName cn = info.getTargetComponent();
return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
}
+
+ @Override
+ public boolean isInstantApp(String packageName, int userId) {
+ if (!Process.myUserHandle().equals(UserHandle.of(userId))) {
+ // Instant app can only exist on current user
+ return false;
+ }
+ try {
+ return mPM.isInstantApp(packageName);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to determine whether package is instant app " + packageName, e);
+ return false;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 0e0b022..5784c37 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -15,12 +15,13 @@
*/
package com.android.quickstep;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.FLOATING_SEARCH_BAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
@@ -267,7 +268,9 @@
@Override
public boolean allowAllAppsFromOverview() {
- return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get();
+ return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
+ // If floating search bar would not show in overview, don't allow all apps gesture.
+ && OVERVIEW.areElementsVisible(getCreatedActivity(), FLOATING_SEARCH_BAR);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index c18ad5a..f1660ee 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -129,7 +129,7 @@
mWindowMaxDeltaY = mLauncher.getResources().getDimensionPixelSize(
R.dimen.swipe_back_window_max_delta_y);
mCancelInterpolator =
- AnimationUtils.loadInterpolator(mLauncher, R.interpolator.back_cancel);
+ AnimationUtils.loadInterpolator(mLauncher, R.interpolator.standard_interpolator);
}
/**
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index fd5c1a7..a9d8afc 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -15,10 +15,10 @@
*/
package com.android.quickstep;
+import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.mapBoundToRange;
-import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 07db194..42bf1ac 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -37,7 +37,6 @@
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -141,6 +140,11 @@
mPendingCommands.clear();
}
+ @UiThread
+ public boolean canStartHomeSafely() {
+ return mPendingCommands.isEmpty() || mPendingCommands.get(0).type == TYPE_HOME;
+ }
+
@Nullable
private TaskView getNextTask(RecentsView view) {
final TaskView runningTaskView = view.getRunningTaskView();
@@ -186,11 +190,6 @@
&& dp != null
&& (dp.isTablet || dp.isTwoPanels);
- if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
- // TODO(b/268075592): add support for quickswitch to/from desktop
- allowQuickSwitch = false;
- }
-
if (cmd.type == TYPE_HIDE) {
if (!allowQuickSwitch) {
return true;
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a8f3c3a..60713cf 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -38,8 +38,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
-import com.android.launcher3.tracing.OverviewComponentObserverProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -276,19 +274,6 @@
}
/**
- * Used for winscope tracing, see launcher_trace.proto
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param serviceProto The parent of this proto message.
- */
- public void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
- OverviewComponentObserverProto.Builder overviewComponentObserver =
- OverviewComponentObserverProto.newBuilder();
- overviewComponentObserver.setOverviewActivityStarted(mActivityInterface.isStarted());
- overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
- serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
- }
-
- /**
* Starts the intent for the current home activity.
*/
public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 5f589bf..d1939ef 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.Looper;
+import android.os.Trace;
import android.os.UserManager;
import android.util.Log;
import android.view.ThreadedRenderer;
@@ -60,5 +62,14 @@
// Elevate GPU priority for Quickstep and Remote animations.
ThreadedRenderer.setContextPriority(
ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+
+ // Enable Looper trace points.
+ // This allows us to see Handler callbacks on traces.
+ Looper.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
+
+ if (BuildConfig.IS_STUDIO_BUILD) {
+ BinderTracker.startTracking(call -> Log.e("BinderCall",
+ call.descriptor + " called on mainthread under " + call.activeTrace));
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 4c9cf8b..031d409 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -80,8 +80,7 @@
}
case TestProtocol.REQUEST_HAS_TIS: {
- response.putBoolean(
- TestProtocol.REQUEST_HAS_TIS, true);
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true);
return response;
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 38ac5bb..34817c0 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -308,7 +308,6 @@
task.setLastSnapshotData(taskInfo);
task.positionInParent = taskInfo.positionInParent;
task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
- // TODO(b/244348395): tasks should be sorted from oldest to most recently used
tasks.add(task);
}
return new DesktopTask(tasks);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 39af7fd..72439de 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
@@ -38,6 +37,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Trace;
+import android.util.Log;
import android.view.Display;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
@@ -46,8 +46,10 @@
import android.window.RemoteTransition;
import android.window.SplashScreen;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@@ -56,7 +58,6 @@
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.model.data.ItemInfo;
@@ -66,6 +67,7 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.FallbackTaskbarUIController;
import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.RunnableList;
@@ -107,7 +109,6 @@
private FallbackRecentsView mFallbackRecentsView;
private OverviewActionsView mActionsView;
private TISBindHelper mTISBindHelper;
- private @Nullable TaskbarManager mTaskbarManager;
private @Nullable FallbackTaskbarUIController mTaskbarUIController;
private StateManager<RecentsState> mStateManager;
@@ -119,6 +120,7 @@
// animation callback
private final Handler mHandler = new Handler();
private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
+ private SplitSelectStateController mSplitSelectStateController;
/**
* Init drag layer and overview panel views.
@@ -130,22 +132,21 @@
mScrimView = findViewById(R.id.scrim_view);
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
- SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
-
- SplitSelectStateController controller =
+ getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
+ mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
- null /* depthController */, getStatsLogManager(),
+ null /* depthController */, getStatsLogManager(),
SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView, controller);
+ mFallbackRecentsView.init(mActionsView, mSplitSelectStateController);
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
private void onTISConnected(TouchInteractionService.TISBinder binder) {
- mTaskbarManager = binder.getTaskbarManager();
- if (mTaskbarManager != null) {
- mTaskbarManager.setActivity(this);
+ TaskbarManager taskbarManager = binder.getTaskbarManager();
+ if (taskbarManager != null) {
+ taskbarManager.setActivity(this);
}
}
@@ -241,6 +242,11 @@
}
final TaskView taskView = (TaskView) v;
+ final RecentsView recentsView = taskView.getRecentsView();
+ if (recentsView == null) {
+ return super.getActivityLaunchOptions(v, item);
+ }
+
RunnableList onEndCallback = new RunnableList();
mActivityLaunchAnimationRunner = new RemoteAnimationFactory() {
@@ -249,7 +255,7 @@
RemoteAnimationTarget[] wallpaperTargets,
RemoteAnimationTarget[] nonAppTargets, AnimationResult result) {
mHandler.removeCallbacks(mAnimationStartTimeoutRunnable);
- AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
+ AnimatorSet anim = composeRecentsLaunchAnimator(recentsView, taskView, appTargets,
wallpaperTargets, nonAppTargets);
anim.addListener(resetStateListener());
result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy,
@@ -277,6 +283,8 @@
activityOptions.options.setLaunchDisplayId(
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
: Display.DEFAULT_DISPLAY);
+ activityOptions.options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
mHandler.postDelayed(mAnimationStartTimeoutRunnable, RECENTS_ANIMATION_TIMEOUT);
return activityOptions;
}
@@ -284,14 +292,16 @@
/**
* Composes the animations for a launch from the recents list if possible.
*/
- private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
+ private AnimatorSet composeRecentsLaunchAnimator(
+ @NonNull RecentsView recentsView,
+ @NonNull TaskView taskView,
RemoteAnimationTarget[] appTargets,
RemoteAnimationTarget[] wallpaperTargets,
RemoteAnimationTarget[] nonAppTargets) {
AnimatorSet target = new AnimatorSet();
boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- createRecentsWindowAnimator(taskView, !activityClosing, appTargets,
+ createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets,
wallpaperTargets, nonAppTargets, null /* depthController */, pa);
target.play(pa.buildAnim());
@@ -299,7 +309,7 @@
if (activityClosing) {
Animator adjacentAnimation = mFallbackRecentsView
.createAdjacentPageAnimForTaskLaunch(taskView);
- adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+ adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE);
adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION);
adjacentAnimation.addListener(resetStateListener());
target.play(adjacentAnimation);
@@ -384,11 +394,8 @@
super.onDestroy();
ACTIVITY_TRACKER.onActivityDestroyed(this);
mActivityLaunchAnimationRunner = null;
-
+ mSplitSelectStateController.onDestroy();
mTISBindHelper.onDestroy();
- if (mTaskbarManager != null) {
- mTaskbarManager.clearActivity(this);
- }
}
@Override
@@ -398,6 +405,7 @@
}
public void startHome() {
+ Log.d(TestProtocol.INCORRECT_HOME_STATE, "start home from recents activity");
RecentsView recentsView = getOverviewPanel();
recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true,
this::startHomeInternal));
@@ -471,4 +479,9 @@
}
};
}
+
+ public boolean canStartHomeSafely() {
+ OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+ return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index f8e09e1..8972dc8 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -115,8 +115,8 @@
* {@link RecentsAnimationCallbacks#onTasksAppeared}}.
*/
@UiThread
- public void removeTaskTarget(@NonNull RemoteAnimationTarget target) {
- UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(target.taskId));
+ public void removeTaskTarget(int targetTaskId) {
+ UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(targetTaskId));
}
@UiThread
@@ -167,7 +167,6 @@
/* event= */ "finishRecentsAnimation",
/* extras= */ toRecents,
/* gestureEvent= */ FINISH_RECENTS_ANIMATION);
-
// Finish not yet requested
mFinishRequested = true;
mFinishTargetIsLauncher = toRecents;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 64ec1d8..1448a52 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,7 +17,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
@@ -52,10 +51,8 @@
import android.graphics.Region;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.UserManager;
import android.provider.Settings;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -68,7 +65,6 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.NavBarPosition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -116,15 +112,6 @@
private final boolean mIsOneHandedModeSupported;
private boolean mPipIsActive;
- private boolean mIsUserUnlocked;
- private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
- private final SimpleBroadcastReceiver mUserUnlockedReceiver = new SimpleBroadcastReceiver(i -> {
- if (ACTION_USER_UNLOCKED.equals(i.getAction())) {
- mIsUserUnlocked = true;
- notifyUserUnlocked();
- }
- });
-
private int mGestureBlockingTaskId = -1;
private @NonNull Region mExclusionRegion = new Region();
private SystemGestureExclusionListenerCompat mExclusionListener;
@@ -150,14 +137,6 @@
runOnDestroy(mRotationTouchHelper::destroy);
}
- // Register for user unlocked if necessary
- mIsUserUnlocked = context.getSystemService(UserManager.class)
- .isUserUnlocked(Process.myUserHandle());
- if (!mIsUserUnlocked) {
- mUserUnlockedReceiver.register(mContext, ACTION_USER_UNLOCKED);
- }
- runOnDestroy(() -> mUserUnlockedReceiver.unregisterReceiverSafely(mContext));
-
// Register for exclusion updates
mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
@Override
@@ -317,39 +296,12 @@
}
/**
- * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
- * will be called back immediately.
- */
- public void runOnUserUnlocked(Runnable action) {
- if (mIsUserUnlocked) {
- action.run();
- } else {
- mUserUnlockedActions.add(action);
- }
- }
-
- /**
- * @return whether the user is unlocked.
- */
- public boolean isUserUnlocked() {
- return mIsUserUnlocked;
- }
-
- /**
* @return whether the user has completed setup wizard
*/
public boolean isUserSetupComplete() {
return mIsUserSetupComplete;
}
- private void notifyUserUnlocked() {
- for (Runnable action : mUserUnlockedActions) {
- action.run();
- }
- mUserUnlockedActions.clear();
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext);
- }
-
/**
* Sets the task id where gestures should be blocked
*/
@@ -386,8 +338,16 @@
boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
|| mRotationTouchHelper.isTaskListFrozen();
- return canStartWithNavHidden
- && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
+ return canStartWithNavHidden && canStartTrackpadGesture();
+ }
+
+ /**
+ * @return whether SystemUI is in a state where we can start a system gesture from the trackpad.
+ * Trackpad gestures can start even when the nav bar / task bar is hidden in sticky immersive
+ * mode.
+ */
+ public boolean canStartTrackpadGesture() {
+ return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
@@ -607,7 +567,6 @@
pw.println(" assistantAvailable=" + mAssistantAvailable);
pw.println(" assistantDisabled="
+ QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
- pw.println(" isUserUnlocked=" + mIsUserUnlocked);
pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
pw.println(" deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 8626c40..2d47097 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -19,6 +19,7 @@
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadScroll;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
@@ -232,16 +233,18 @@
/**
* @return whether the coordinates of the {@param event} is in the swipe up gesture region.
*/
- public boolean isInSwipeUpTouchRegion(MotionEvent event, BaseActivityInterface activity) {
- return isInSwipeUpTouchRegion(event, 0, activity);
+ public boolean isInSwipeUpTouchRegion(MotionEvent event) {
+ return isInSwipeUpTouchRegion(event, 0);
}
/**
* @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
* is in the swipe up gesture region.
*/
- public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex,
- BaseActivityInterface activity) {
+ public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+ if (isTrackpadScroll(event)) {
+ return false;
+ }
if (isTrackpadMultiFingerSwipe(event)) {
return true;
}
diff --git a/quickstep/src/com/android/quickstep/SplitSelectionListener.kt b/quickstep/src/com/android/quickstep/SplitSelectionListener.kt
new file mode 100644
index 0000000..5025c1c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SplitSelectionListener.kt
@@ -0,0 +1,17 @@
+package com.android.quickstep
+
+interface SplitSelectionListener {
+ /** Called when the first app has been selected with the intention to launch split screen */
+ fun onSplitSelectionActive()
+
+ /** Called when the second app has been selected with the intention to launch split screen */
+ fun onSplitSelectionConfirmed()
+
+ /**
+ * Called when the user no longer is in the process of selecting apps for split screen.
+ * [launchedSplit] will be true if selected apps have launched successfully (either in
+ * split screen or fullscreen), false if the user canceled/exited the selection process
+ */
+ fun onSplitSelectionExit(launchedSplit: Boolean) {
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 25ac47a..e481165 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -15,8 +15,8 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
+import static com.android.app.animation.Interpolators.LINEAR;
import android.animation.Animator;
import android.content.Context;
@@ -218,7 +218,7 @@
if (progress >= end) {
return 0f;
}
- return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+ return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 9350c72..fae929a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.LogUtils.splitFailureMessage;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -61,6 +62,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -74,6 +76,7 @@
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.desktopmode.IDesktopTaskListener;
import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
@@ -82,6 +85,7 @@
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
import com.android.wm.shell.transition.IShellTransitions;
@@ -126,10 +130,12 @@
private IPipAnimationListener mPipAnimationListener;
private IBubblesListener mBubblesListener;
private ISplitScreenListener mSplitScreenListener;
+ private ISplitSelectListener mSplitSelectListener;
private IStartingWindowListener mStartingWindowListener;
private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
private IRecentTasksListener mRecentTasksListener;
private IUnfoldTransitionListener mUnfoldAnimationListener;
+ private IDesktopTaskListener mDesktopTaskListener;
private final LinkedHashMap<RemoteTransition, TransitionFilter> mRemoteTransitions =
new LinkedHashMap<>();
private IBinder mOriginalTransactionToken = null;
@@ -236,6 +242,7 @@
setPipAnimationListener(mPipAnimationListener);
setBubblesListener(mBubblesListener);
registerSplitScreenListener(mSplitScreenListener);
+ registerSplitSelectListener(mSplitSelectListener);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(mLauncherUnlockAnimationController);
new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
@@ -243,6 +250,9 @@
registerRecentTasksListener(mRecentTasksListener);
setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
setUnfoldAnimationListener(mUnfoldAnimationListener);
+ setDesktopTaskListener(mDesktopTaskListener);
+ setAssistantOverridesRequested(
+ AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
}
/**
@@ -311,13 +321,24 @@
@MainThread
@Override
- public void onStatusBarMotionEvent(MotionEvent event) {
+ public void onStatusBarTouchEvent(MotionEvent event) {
Preconditions.assertUIThread();
if (mSystemUiProxy != null) {
try {
- mSystemUiProxy.onStatusBarMotionEvent(event);
+ mSystemUiProxy.onStatusBarTouchEvent(event);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call onStatusBarMotionEvent", e);
+ Log.w(TAG, "Failed call onStatusBarTouchEvent with arg: " + event, e);
+ }
+ }
+ }
+
+ @Override
+ public void onStatusBarTrackpadEvent(MotionEvent event) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onStatusBarTrackpadEvent(event);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onStatusBarTrackpadEvent with arg: " + event, e);
}
}
}
@@ -356,6 +377,17 @@
}
@Override
+ public void setAssistantOverridesRequested(int[] invocationTypes) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.setAssistantOverridesRequested(invocationTypes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setAssistantOverridesRequested", e);
+ }
+ }
+ }
+
+ @Override
public void notifyAccessibilityButtonClicked(int displayId) {
if (mSystemUiProxy != null) {
try {
@@ -631,13 +663,15 @@
/**
* Tells SysUI to show the bubble with the provided key.
* @param key the key of the bubble to show.
- * @param onLauncherHome whether the bubble is showing on launcher home or not (modifies where
- * the expanded bubble view is placed).
+ * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
+ * axis.
+ * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
+ * axis.
*/
- public void showBubble(String key, boolean onLauncherHome) {
+ public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
if (mBubbles != null) {
try {
- mBubbles.showBubble(key, onLauncherHome);
+ mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
} catch (RemoteException e) {
Log.w(TAG, "Failed call showBubble");
}
@@ -645,6 +679,31 @@
}
/**
+ * Tells SysUI to remove the bubble with the provided key.
+ * @param key the key of the bubble to show.
+ */
+ public void removeBubble(String key) {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.removeBubble(key);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeBubble");
+ }
+ }
+
+ /**
+ * Tells SysUI to remove all bubbles.
+ */
+ public void removeAllBubbles() {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.removeAllBubbles();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeAllBubbles");
+ }
+ }
+
+ /**
* Tells SysUI to collapse the bubbles.
*/
public void collapseBubbles() {
@@ -657,6 +716,21 @@
}
}
+ /**
+ * Tells SysUI when the bubble is being dragged.
+ * Should be called only when the bubble bar is expanded.
+ * @param bubbleKey the key of the bubble to collapse/expand
+ * @param isBeingDragged whether the bubble is being dragged
+ */
+ public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.onBubbleDrag(bubbleKey, isBeingDragged);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onBubbleDrag");
+ }
+ }
+
//
// Splitscreen
//
@@ -683,6 +757,28 @@
mSplitScreenListener = null;
}
+ public void registerSplitSelectListener(ISplitSelectListener listener) {
+ if (mSplitScreen != null) {
+ try {
+ mSplitScreen.registerSplitSelectListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerSplitSelectListener");
+ }
+ }
+ mSplitSelectListener = listener;
+ }
+
+ public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+ if (mSplitScreen != null) {
+ try {
+ mSplitScreen.unregisterSplitSelectListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call unregisterSplitSelectListener");
+ }
+ }
+ mSplitSelectListener = null;
+ }
+
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
@SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
@@ -692,7 +788,7 @@
mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startTasks");
+ Log.w(TAG, splitFailureMessage("startTasks", "RemoteException"), e);
}
}
}
@@ -705,7 +801,7 @@
mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
splitPosition, splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntentAndTask");
+ Log.w(TAG, splitFailureMessage("startIntentAndTask", "RemoteException"), e);
}
}
}
@@ -721,7 +817,7 @@
pendingIntent2, userId2, shortcutInfo2, options2, splitPosition, splitRatio,
remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntents");
+ Log.w(TAG, splitFailureMessage("startIntents", "RemoteException"), e);
}
}
}
@@ -734,7 +830,7 @@
mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
splitPosition, splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startShortcutAndTask");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTask", "RemoteException"), e);
}
}
}
@@ -750,7 +846,8 @@
mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
splitPosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startTasksWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startTasksWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -764,7 +861,8 @@
mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startIntentAndTaskWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -777,7 +875,8 @@
mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
taskId, options2, splitPosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startShortcutAndTaskWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -797,7 +896,8 @@
shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
sidePosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntentsWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startIntentsWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -809,7 +909,7 @@
mSplitScreen.startShortcut(packageName, shortcutId, position, options,
user, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startShortcut");
+ Log.w(TAG, splitFailureMessage("startShortcut", "RemoteException"), e);
}
}
}
@@ -821,7 +921,7 @@
mSplitScreen.startIntent(intent, userId, fillInIntent, position, options,
instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntent");
+ Log.w(TAG, splitFailureMessage("startIntent", "RemoteException"), e);
}
}
}
@@ -1161,6 +1261,41 @@
}
}
+ /** Call shell to stash desktop apps */
+ public void stashDesktopApps(int displayId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.stashDesktopApps(displayId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stashDesktopApps", e);
+ }
+ }
+ }
+
+ /** Call shell to hide desktop apps that may be stashed */
+ public void hideStashedDesktopApps(int displayId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.hideStashedDesktopApps(displayId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call hideStashedDesktopApps", e);
+ }
+ }
+ }
+
+ /**
+ * If task with the given id is on the desktop, bring it to front
+ */
+ public void showDesktopApp(int taskId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.showDesktopApp(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call showDesktopApp", e);
+ }
+ }
+ }
+
/** Call shell to get number of visible freeform tasks */
public int getVisibleDesktopTaskCount(int displayId) {
if (mDesktopMode != null) {
@@ -1173,6 +1308,29 @@
return 0;
}
+ /** Set a listener on shell to get updates about desktop task state */
+ public void setDesktopTaskListener(@Nullable IDesktopTaskListener listener) {
+ mDesktopTaskListener = listener;
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.setTaskListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setDesktopTaskListener", e);
+ }
+ }
+ }
+
+ /** Perform cleanup transactions after animation to split select is complete */
+ public void onDesktopSplitSelectAnimComplete(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.onDesktopSplitSelectAnimComplete(taskInfo);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onDesktopSplitSelectAnimComplete", e);
+ }
+ }
+ }
+
//
// Unfold transition
//
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index eacca0d..2896fe0 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -38,6 +38,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
@@ -61,7 +62,7 @@
private RecentsAnimationTargets mTargets;
// Temporary until we can hook into gesture state events
private GestureState mLastGestureState;
- private RemoteAnimationTarget mLastAppearedTaskTarget;
+ private RemoteAnimationTarget[] mLastAppearedTaskTargets;
private Runnable mLiveTileCleanUpHandler;
private Context mCtx;
@@ -124,13 +125,14 @@
// If mCallbacks still != null, that means we are getting this startRecentsAnimation()
// before the previous one got onRecentsAnimationStart(). In that case, cleanup the
// previous animation so it doesn't mess up/listen to state changes in this animation.
- cleanUpRecentsAnimation();
+ cleanUpRecentsAnimation(mCallbacks);
}
final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
mLastGestureState = gestureState;
- mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx),
- activityInterface.allowMinimizeSplitScreen());
+ RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
+ SystemUiProxy.INSTANCE.get(mCtx), activityInterface.allowMinimizeSplitScreen());
+ mCallbacks = newCallbacks;
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
@@ -143,18 +145,25 @@
}
mController = controller;
mTargets = targets;
- mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId());
- mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
+ // TODO(b/236226779): We can probably get away w/ setting mLastAppearedTaskTargets
+ // to all appeared targets directly vs just looking at running ones
+ int[] runningTaskIds = mLastGestureState.getRunningTaskIds(targets.apps.length > 1);
+ mLastAppearedTaskTargets = new RemoteAnimationTarget[runningTaskIds.length];
+ for (int i = 0; i < runningTaskIds.length; i++) {
+ RemoteAnimationTarget task = mTargets.findTask(runningTaskIds[i]);
+ mLastAppearedTaskTargets[i] = task;
+ }
+ mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
}
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
- cleanUpRecentsAnimation();
+ cleanUpRecentsAnimation(newCallbacks);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- cleanUpRecentsAnimation();
+ cleanUpRecentsAnimation(newCallbacks);
}
@Override
@@ -172,6 +181,9 @@
RecentsView recentsView =
activityInterface.getCreatedActivity().getOverviewPanel();
if (recentsView != null) {
+ Log.d(TestProtocol.INCORRECT_HOME_STATE,
+ "finish recents animation on "
+ + compat.taskInfo.description);
recentsView.finishRecentsAnimation(true, null);
}
return;
@@ -204,14 +216,18 @@
true /*shown*/, null /* animatorHandler */);
}
if (mController != null) {
- if (mLastAppearedTaskTarget == null
- || appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) {
- if (mLastAppearedTaskTarget != null) {
- mController.removeTaskTarget(mLastAppearedTaskTarget);
+ if (mLastAppearedTaskTargets != null) {
+ for (RemoteAnimationTarget lastTarget : mLastAppearedTaskTargets) {
+ for (RemoteAnimationTarget appearedTarget : appearedTaskTargets) {
+ if (lastTarget != null &&
+ appearedTarget.taskId != lastTarget.taskId) {
+ mController.removeTaskTarget(lastTarget.taskId);
+ }
+ }
}
- mLastAppearedTaskTarget = appearedTaskTarget;
- mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
}
+ mLastAppearedTaskTargets = appearedTaskTargets;
+ mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
}
}
@@ -276,7 +292,7 @@
mCallbacks.addListener(gestureState);
gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED
| STATE_RECENTS_ANIMATION_STARTED);
- gestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
+ gestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
return mCallbacks;
}
@@ -315,11 +331,10 @@
* Finishes the running recents animation.
* @param forceFinish will synchronously finish the controller
*/
- private void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
+ public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
if (mController != null) {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "finishRunningRecentsAnimation", toHome);
- mCallbacks.notifyAnimationCanceled();
if (forceFinish) {
mController.finishController(toHome, null, false /* sendUserLeaveHint */,
true /* forceFinish */);
@@ -328,7 +343,6 @@
? mController::finishAnimationToHome
: mController::finishAnimationToApp);
}
- cleanUpRecentsAnimation();
}
}
@@ -355,7 +369,12 @@
/**
* Cleans up the recents animation entirely.
*/
- private void cleanUpRecentsAnimation() {
+ private void cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks) {
+ if (mCallbacks != targetCallbacks) {
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
+ return;
+ }
ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
if (mLiveTileCleanUpHandler != null) {
mLiveTileCleanUpHandler.run();
@@ -377,7 +396,7 @@
mCallbacks = null;
mTargets = null;
mLastGestureState = null;
- mLastAppearedTaskTarget = null;
+ mLastAppearedTaskTargets = null;
}
@Nullable
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 7c05a10..164a366 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import android.annotation.Nullable;
@@ -182,17 +181,14 @@
}
}
- // Loading content descriptions if accessibility or low RAM recents is enabled.
- if (GO_LOW_RAM_RECENTS_ENABLED || mAccessibilityManager.isEnabled()) {
- // Skip loading the content description if the activity no longer exists
- if (activityInfo == null) {
- activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
- key.getComponent(), key.userId);
- }
- if (activityInfo != null) {
- entry.contentDescription = getBadgedContentDescription(
- activityInfo, task.key.userId, task.taskDescription);
- }
+ // Skip loading the content description if the activity no longer exists
+ if (activityInfo == null) {
+ activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
+ key.getComponent(), key.userId);
+ }
+ if (activityInfo != null) {
+ entry.contentDescription = getBadgedContentDescription(
+ activityInfo, task.key.userId, task.taskDescription);
}
mIconCache.put(task.key, entry);
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 1744b08..076f4b1 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -122,6 +122,12 @@
public void removeListeners() {
}
+ /**
+ * Clears any active state outside of the TaskOverlay lifecycle which might have built
+ * up over time
+ */
+ public void clearAllActiveState() { }
+
/** Note that these will be shown in order from top to bottom, if available for the task. */
private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
TaskShortcutFactory.APP_INFO,
@@ -159,6 +165,10 @@
return mActionsView;
}
+ public TaskThumbnailView getThumbnailView() {
+ return mThumbnailView;
+ }
+
/**
* Called when the current task is interactive for the user
*/
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 208f99a..901690b 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -140,6 +140,7 @@
@Override
public void onClick(View view) {
+ dismissTaskMenuView(mTarget);
((RecentsView) mTarget.getOverviewPanel())
.getSplitSelectController().getAppPairsController().saveAppPair(mTaskView);
}
@@ -396,11 +397,12 @@
@Override
public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer) {
- return InstantAppResolver.newInstance(activity).isInstantApp(activity,
- taskContainer.getTask().getTopComponent().getPackageName()) ?
- Collections.singletonList(new SystemShortcut.Install(activity,
- taskContainer.getItemInfo(), taskContainer.getTaskView())) :
- null;
+ Task t = taskContainer.getTask();
+ return InstantAppResolver.newInstance(activity).isInstantApp(
+ t.getTopComponent().getPackageName(), t.getKey().userId)
+ ? Collections.singletonList(new SystemShortcut.Install(activity,
+ taskContainer.getItemInfo(), taskContainer.getTaskView()))
+ : null;
}
};
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 67360c4..80a449b 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -33,6 +33,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.TraceHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -51,7 +52,8 @@
* TODO: remove this once we switch to getting the icon and label from IconCache.
*/
public static CharSequence getTitle(Context context, Task task) {
- return getTitle(context, task.key.userId, task.getTopComponent().getPackageName());
+ return TraceHelper.allowIpcs("TaskUtils.getTitle", () ->
+ getTitle(context, task.key.userId, task.getTopComponent().getPackageName()));
}
public static CharSequence getTitle(
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 1238819..af49774 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -21,6 +21,9 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.TOUCH_RESPONSE;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
@@ -35,9 +38,8 @@
import static com.android.launcher3.QuickstepTransitionManager.SPLIT_DIVIDER_ANIM_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.testing.shared.TestProtocol.LAUNCH_SPLIT_PAIR;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
@@ -61,12 +63,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
@@ -160,13 +162,14 @@
}
public static void createRecentsWindowAnimator(
- @NonNull TaskView v, boolean skipViewChanges,
+ @NonNull RecentsView recentsView,
+ @NonNull TaskView v,
+ boolean skipViewChanges,
@NonNull RemoteAnimationTarget[] appTargets,
@NonNull RemoteAnimationTarget[] wallpaperTargets,
@NonNull RemoteAnimationTarget[] nonAppTargets,
@Nullable DepthController depthController,
PendingAnimation out) {
- RecentsView recentsView = v.getRecentsView();
boolean isQuickSwitch = v.isEndQuickswitchCuj();
v.setEndQuickswitchCuj(false);
@@ -237,12 +240,12 @@
for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
out.setFloat(tvsLocal.fullScreenProgress,
- AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ AnimatedFloat.VALUE, 1, TOUCH_RESPONSE);
out.setFloat(tvsLocal.recentsViewScale,
AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
- TOUCH_RESPONSE_INTERPOLATOR);
+ TOUCH_RESPONSE);
out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
- TOUCH_RESPONSE_INTERPOLATOR);
+ TOUCH_RESPONSE);
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -355,9 +358,9 @@
float fullScreenScale =
topMostSimulators[i].getTaskViewSimulator().getFullScreenScale();
out.addFloat(ttv, VIEW_TRANSLATE_Y, translationY,
- translationY / fullScreenScale, TOUCH_RESPONSE_INTERPOLATOR);
+ translationY / fullScreenScale, TOUCH_RESPONSE);
out.addFloat(ttv, VIEW_TRANSLATE_X, translationX,
- translationX / fullScreenScale, TOUCH_RESPONSE_INTERPOLATOR);
+ translationX / fullScreenScale, TOUCH_RESPONSE);
}
Matrix[] k0i = new Matrix[matrixSize];
@@ -405,31 +408,26 @@
if (depthController != null) {
out.setFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE,
- BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE_INTERPOLATOR);
+ BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE);
}
}
/**
- * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
- * if needed
+ * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
+ * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
+ * Technically this case should be taken care of by
+ * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether
+ * it's a single task or multiple tasks results in different entry-points.
*
- * We could manually try to animate the just the bounds for the leashes we get back, but we try
- * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
- * us.
- *
- * First you have to call TVS#setPreview() to indicate which leash it will operate one
- * Then operations happen in TVS#apply() on each frame callback.
- *
- * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
- * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
- * device is considered in multiWindowMode and things like insets and stuff change
- * and calculations have to be adjusted in the animations for that
+ * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
+ * case where launcher handles animating starting split tasks from app icon)
*/
public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView,
@NonNull StateManager stateManager, @Nullable DepthController depthController,
int initialTaskId, int secondTaskId, @NonNull TransitionInfo transitionInfo,
SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
if (launchingTaskView != null) {
+ testLogD(LAUNCH_SPLIT_PAIR, "composeRecentsSplitLaunchAnimator taskView not-null");
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
@@ -457,12 +455,15 @@
return;
}
- // TODO: consider initialTaskPendingIntent
TransitionInfo.Change splitRoot1 = null;
TransitionInfo.Change splitRoot2 = null;
+ final ArrayList<SurfaceControl> openingTargets = new ArrayList<>();
for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
- if (change.getTaskInfo() == null) continue;
+ if (change.getTaskInfo() == null) {
+ testLogD(LAUNCH_SPLIT_PAIR, "changeTaskInfo null; change: " + change);
+ continue;
+ }
final int taskId = change.getTaskInfo().taskId;
final int mode = change.getMode();
@@ -477,31 +478,48 @@
if (taskId == initialTaskId) {
splitRoot1 = change.getParent() == null ? change :
transitionInfo.getChange(change.getParent());
+ openingTargets.add(splitRoot1.getLeash());
}
if (taskId == secondTaskId) {
splitRoot2 = change.getParent() == null ? change :
transitionInfo.getChange(change.getParent());
+ openingTargets.add(splitRoot2.getLeash());
}
}
- // This is where we should animate the split roots. For now, though, just make them visible.
- animateSplitRoot(t, splitRoot1);
- animateSplitRoot(t, splitRoot2);
+ SurfaceControl.Transaction animTransaction = new SurfaceControl.Transaction();
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(SPLIT_LAUNCH_DURATION);
+ animator.addUpdateListener(valueAnimator -> {
+ float progress = valueAnimator.getAnimatedFraction();
+ for (SurfaceControl leash: openingTargets) {
+ animTransaction.setAlpha(leash, progress);
+ }
+ animTransaction.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ for (SurfaceControl leash: openingTargets) {
+ animTransaction.show(leash)
+ .setAlpha(leash, 0.0f);
+ }
+ animTransaction.apply();
+ }
- // This contains the initial state (before animation), so apply this at the beginning of
- // the animation.
- t.apply();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishCallback.run();
+ }
+ });
- // Once there is an animation, this should be called AFTER the animation completes.
- finishCallback.run();
- }
-
- private static void animateSplitRoot(SurfaceControl.Transaction t,
- TransitionInfo.Change splitRoot) {
- if (splitRoot != null) {
- t.show(splitRoot.getLeash());
- t.setAlpha(splitRoot.getLeash(), 1.f);
+ if (splitRoot1 != null && splitRoot1.getParent() != null) {
+ // Set the highest level split root alpha; we could technically use the parent of either
+ // splitRoot1 or splitRoot2
+ t.setAlpha(transitionInfo.getChange(splitRoot1.getParent()).getLeash(), 1f);
}
+ t.apply();
+ animator.start();
}
/**
@@ -514,7 +532,9 @@
* it's a single task or multiple tasks results in different entry-points.
*
* If it is null, then it will simply fade in the starting apps and fade out launcher (for the
- * case where launcher handles animating starting split tasks from app icon) */
+ * case where launcher handles animating starting split tasks from app icon)
+ * @deprecated with shell transitions
+ */
public static void composeRecentsSplitLaunchAnimatorLegacy(
@Nullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId,
@NonNull RemoteAnimationTarget[] appTargets,
@@ -606,8 +626,8 @@
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
- nonAppTargets, depthController, pa);
+ createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets,
+ wallpaperTargets, nonAppTargets, depthController, pa);
if (launcherClosing) {
// TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/,
@@ -637,7 +657,7 @@
raController.setWillFinishToHome(false);
}
launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
- launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+ launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index d34cddf..01baed3 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -210,7 +210,7 @@
@Nullable
private final RunningTaskInfo mTopTask;
- private final List<RunningTaskInfo> mAllCachedTasks;
+ public final List<RunningTaskInfo> mAllCachedTasks;
CachedTaskInfo(List<RunningTaskInfo> allCachedTasks) {
mAllCachedTasks = allCachedTasks;
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d6a468d..bf6150a 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,8 +24,11 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
@@ -37,10 +40,8 @@
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
@@ -56,6 +57,8 @@
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.Service;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@@ -66,8 +69,10 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.Log;
import android.view.Choreographer;
+import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -82,27 +87,26 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.provider.RestoreDbTask;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.LauncherTraceProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.uioverrides.flags.FlagsFactory;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -110,13 +114,13 @@
import com.android.quickstep.inputconsumers.ProgressDelegateInputConsumer;
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
-import com.android.quickstep.inputconsumers.StatusBarInputConsumer;
import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer;
+import com.android.quickstep.inputconsumers.TrackpadStatusBarInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
-import com.android.quickstep.util.ProtoTracer;
-import com.android.quickstep.util.ProxyScreenStatusProvider;
+import com.android.quickstep.util.AssistStateManager;
+import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -124,7 +128,6 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
-import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
@@ -140,8 +143,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.util.Arrays;
-import java.util.LinkedList;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -149,8 +150,7 @@
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.R)
-public class TouchInteractionService extends Service
- implements ProtoTraceable<LauncherTraceProto.Builder> {
+public class TouchInteractionService extends Service {
private static final String SUBSTRING_PREFIX = "; ";
private static final String NEWLINE_PREFIX = "\n\t\t\t-> ";
@@ -284,6 +284,20 @@
}));
}
+ /**
+ * Sent when the assistant has been invoked with the given type (defined in AssistManager)
+ * and should be shown. This method is used if SystemUiProxy#setAssistantOverridesRequested
+ * was previously called including this invocation type.
+ */
+ @Override
+ public void onAssistantOverrideInvoked(int invocationType) {
+ executeForTouchInteractionService(tis -> {
+ if (!AssistUtils.newInstance(tis).tryStartAssistOverride(invocationType)) {
+ Log.w(TAG, "Failed to invoke Assist override");
+ }
+ });
+ }
+
@Override
public void onNavigationBarSurface(SurfaceControl surface) {
// TODO: implement
@@ -306,24 +320,6 @@
@BinderThread
@Override
- public void onScreenTurnedOn() {
- MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
- }
-
- @BinderThread
- @Override
- public void onScreenTurningOn() {
- MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOn);
- }
-
- @BinderThread
- @Override
- public void onScreenTurningOff() {
- MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOff);
- }
-
- @BinderThread
- @Override
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
executeForTouchInteractionService(tis -> {
StatefulActivity activity =
@@ -488,11 +484,9 @@
BootAwarePreloader.start(this);
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
- mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
- mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
+ LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
+ LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
-
- ProtoTracer.INSTANCE.get(this).add(this);
sConnected = true;
}
@@ -511,7 +505,7 @@
private void initInputMonitor(String reason) {
disposeEventHandlers("Initializing input monitor due to: " + reason);
- if (mDeviceState.isButtonNavMode()) {
+ if (mDeviceState.isButtonNavMode() && !ENABLE_TRACKPAD_GESTURE.get()) {
return;
}
@@ -559,7 +553,7 @@
}
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
- if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
+ if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
// Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
// mode doesn't have gestures
return;
@@ -579,15 +573,7 @@
AccessibilityManager am = getSystemService(AccessibilityManager.class);
if (isHomeAndOverviewSame) {
- Intent intent = new Intent(mOverviewComponentObserver.getHomeIntent())
- .setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
- RemoteAction allAppsAction = new RemoteAction(
- Icon.createWithResource(this, R.drawable.ic_apps),
- getString(R.string.all_apps_label),
- getString(R.string.all_apps_label),
- PendingIntent.getActivity(this, GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
- am.registerSystemAction(allAppsAction, GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+ am.registerSystemAction(createAllAppsAction(), GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
} else {
am.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
}
@@ -600,26 +586,43 @@
mTISBinder.onOverviewTargetChange();
}
+ private RemoteAction createAllAppsAction() {
+ final Intent homeIntent = new Intent(mOverviewComponentObserver.getHomeIntent())
+ .setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
+ final PendingIntent actionPendingIntent;
+
+ if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ actionPendingIntent = new PendingIntent(new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType,
+ IBinder allowlistToken, IIntentReceiver finishedReceiver,
+ String requiredPermission, Bundle options) {
+ MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps(homeIntent));
+ }
+ });
+ } else {
+ actionPendingIntent = PendingIntent.getActivity(
+ this,
+ GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
+ homeIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ return new RemoteAction(
+ Icon.createWithResource(this, R.drawable.ic_apps),
+ getString(R.string.all_apps_label),
+ getString(R.string.all_apps_label),
+ actionPendingIntent);
+ }
+
@UiThread
private void onSystemUiFlagsChanged(int lastSysUIFlags) {
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
mOverviewComponentObserver.onSystemUiStateChanged();
mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
- boolean wasFreeformActive =
- (lastSysUIFlags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0;
- boolean isFreeformActive =
- (systemUiStateFlags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0;
- if (wasFreeformActive != isFreeformActive) {
- DesktopVisibilityController controller =
- LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
- if (controller != null) {
- controller.setFreeformTasksVisible(isFreeformActive);
- }
- }
-
int isShadeExpandedFlag =
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
boolean wasExpanded = (lastSysUIFlags & isShadeExpandedFlag) != 0;
@@ -629,25 +632,12 @@
// overview.
mTaskAnimationManager.endLiveTile();
}
-
- if ((lastSysUIFlags & SYSUI_STATE_TRACING_ENABLED) !=
- (systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED)) {
- // Update the tracing state
- if ((systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED) != 0) {
- Log.d(TAG, "Starting tracing.");
- ProtoTracer.INSTANCE.get(this).start();
- } else {
- Log.d(TAG, "Stopping tracing. Dumping to file="
- + ProtoTracer.INSTANCE.get(this).getTraceFile());
- ProtoTracer.INSTANCE.get(this).stop();
- }
- }
}
}
@UiThread
private void onAssistantVisibilityChanged() {
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
mDeviceState.getAssistantVisibility());
}
@@ -657,15 +647,13 @@
public void onDestroy() {
Log.d(TAG, "Touch service destroyed: user=" + getUserId());
sIsInitialized = false;
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
mOverviewComponentObserver.onDestroy();
}
disposeEventHandlers("TouchInteractionService onDestroy()");
mDeviceState.destroy();
SystemUiProxy.INSTANCE.get(this).clearProxy();
- ProtoTracer.INSTANCE.get(this).stop();
- ProtoTracer.INSTANCE.get(this).remove(this);
getSystemService(AccessibilityManager.class)
.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
@@ -691,12 +679,12 @@
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked() || (mDeviceState.isButtonNavMode()
+ && !isTrackpadMotionEvent(event))) {
return;
}
- Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
- TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
+ SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
final int action = event.getActionMasked();
// Note this will create a new consumer every mouse click, as after ACTION_UP from the click
@@ -707,8 +695,7 @@
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
if ((!mDeviceState.isOneHandedModeActive()
- && mRotationTouchHelper.isInSwipeUpTouchRegion(event,
- mOverviewComponentObserver.getActivityInterface()))
+ && mRotationTouchHelper.isInSwipeUpTouchRegion(event))
|| isHoverActionWithoutConsumer) {
// Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
// onConsumerInactive and wipe the previous gesture state
@@ -720,7 +707,8 @@
mGestureState = newGestureState;
mConsumer = newConsumer(prevGestureState, mGestureState, event);
mUncheckedConsumer = mConsumer;
- } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
+ } else if (LockedUserState.get(this).isUserUnlocked()
+ && (mDeviceState.isFullyGesturalNavMode() || isTrackpadMultiFingerSwipe(event))
&& mDeviceState.canTriggerAssistantAction(event)) {
mGestureState = createGestureState(mGestureState,
getTrackpadGestureType(event));
@@ -783,10 +771,7 @@
if (mGestureState.isTrackpadGesture() && (action == ACTION_POINTER_DOWN
|| action == ACTION_POINTER_UP)) {
// Skip ACTION_POINTER_DOWN and ACTION_POINTER_UP events from trackpad.
- if (action == ACTION_POINTER_DOWN) {
- mGestureState.setTrackpadGestureType(getTrackpadGestureType(event));
- }
- } else if (event.isHoverEvent()) {
+ } else if (isCursorHoverEvent(event)) {
mUncheckedConsumer.onHoverEvent(event);
} else {
mUncheckedConsumer.onMotionEvent(event);
@@ -795,8 +780,12 @@
if (cleanUpConsumer) {
reset();
}
- TraceHelper.INSTANCE.endFlagsOverride(traceToken);
- ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
+ traceToken.close();
+ }
+
+ // Talkback generates hover events on touch, which we do not want to consume.
+ private boolean isCursorHoverEvent(MotionEvent event) {
+ return event.isHoverEvent() && event.getSource() == InputDevice.SOURCE_MOUSE;
}
private InputConsumer tryCreateAssistantInputConsumer(
@@ -830,7 +819,7 @@
ActiveGestureLog.INSTANCE.getLogId());
taskInfo = previousGestureState.getRunningTask();
gestureState.updateRunningTask(taskInfo);
- gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId());
+ gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds());
gestureState.updatePreviouslyAppearedTaskIds(
previousGestureState.getPreviouslyAppearedTaskIds());
} else {
@@ -862,9 +851,11 @@
return consumer;
}
- boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
+ boolean canStartSystemGesture =
+ mGestureState.isTrackpadGesture() ? mDeviceState.canStartTrackpadGesture()
+ : mDeviceState.canStartSystemGesture();
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
CompoundString reasonString = newCompoundString("device locked");
InputConsumer consumer;
if (canStartSystemGesture) {
@@ -897,11 +888,12 @@
.append(", trying to use default input consumer");
base = getDefaultInputConsumer(reasonString);
}
- if (mDeviceState.isGesturalNavMode()) {
+ if (mDeviceState.isGesturalNavMode() || newGestureState.isTrackpadGesture()) {
handleOrientationSetup(base);
}
- if (mDeviceState.isFullyGesturalNavMode()) {
- String reasonPrefix = "device is in gesture navigation mode";
+ if (mDeviceState.isFullyGesturalNavMode() || newGestureState.isTrackpadGesture()) {
+ String reasonPrefix = "device is in gesture navigation mode or 3-button mode with a"
+ + " trackpad gesture";
if (mDeviceState.canTriggerAssistantAction(event)) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
@@ -924,8 +916,17 @@
.append(SUBSTRING_PREFIX)
.append("TaskbarActivityContext != null, "
+ "using TaskbarUnstashInputConsumer");
- base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac);
+ base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac,
+ mOverviewCommandHelper);
}
+ } else if (canStartSystemGesture && FeatureFlags.ENABLE_LONG_PRESS_NAV_HANDLE.get()
+ && !previousGestureState.isRecentsAnimationRunning()) {
+ reasonString.append(NEWLINE_PREFIX)
+ .append(reasonPrefix)
+ .append(SUBSTRING_PREFIX)
+ .append("Long press nav handle enabled, "
+ + "using NavHandleLongPressInputConsumer");
+ base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat);
}
if (mDeviceState.isBubblesExpanded()) {
@@ -945,11 +946,12 @@
}
if (ENABLE_TRACKPAD_GESTURE.get() && mGestureState.isTrackpadGesture()
- && !previousGestureState.isRecentsAnimationRunning()) {
+ && canStartSystemGesture && !previousGestureState.isRecentsAnimationRunning()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
- .append("Trackpad 3-finger gesture, using StatusBarInputConsumer");
- base = new StatusBarInputConsumer(getBaseContext(), base, mInputMonitorCompat);
+ .append("Trackpad 3-finger gesture, using TrackpadStatusBarInputConsumer");
+ base = new TrackpadStatusBarInputConsumer(getBaseContext(), base,
+ mInputMonitorCompat);
}
if (mDeviceState.isScreenPinningActive()) {
@@ -1037,6 +1039,7 @@
}
reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded");
+
// Use overview input consumer for sharesheets on top of home.
boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
&& gestureState.getRunningTask() != null
@@ -1112,17 +1115,21 @@
private InputConsumer createDeviceLockedInputConsumer(
GestureState gestureState, CompoundString reasonString) {
- if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
+ if ((mDeviceState.isFullyGesturalNavMode() || gestureState.isTrackpadGesture())
+ && gestureState.getRunningTask() != null) {
reasonString.append(SUBSTRING_PREFIX)
- .append("device is in gesture nav mode and running task != null")
+ .append("device is in gesture nav mode or 3-button mode with a trackpad gesture"
+ + "and running task != null")
.append(", using DeviceLockedInputConsumer");
return new DeviceLockedInputConsumer(
this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat);
} else {
return getDefaultInputConsumer(reasonString
.append(SUBSTRING_PREFIX)
- .append(mDeviceState.isFullyGesturalNavMode()
- ? "running task == null" : "device is not in gesture nav mode")
+ .append((mDeviceState.isFullyGesturalNavMode()
+ || gestureState.isTrackpadGesture())
+ ? "running task == null"
+ : "device is not in gesture nav mode and it's not a trackpad gesture")
.append(", trying to use default input consumer"));
}
}
@@ -1211,11 +1218,13 @@
}
private void preloadOverview(boolean fromInit) {
+ Trace.beginSection("preloadOverview(fromInit=" + fromInit + ")");
preloadOverview(fromInit, false);
+ Trace.endSection();
}
private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
@@ -1251,7 +1260,7 @@
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
final BaseActivityInterface activityInterface =
@@ -1261,7 +1270,10 @@
// We only care about the existing background activity.
return;
}
- if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
+ Configuration oldConfig = activity.getResources().getConfiguration();
+ boolean isFoldUnfold = isTablet(oldConfig) != isTablet(newConfig);
+ if (!isFoldUnfold && mOverviewComponentObserver.canHandleConfigChanges(
+ activity.getComponentName(),
activity.getResources().getConfiguration().diff(newConfig))) {
// Since navBar gestural height are different between portrait and landscape,
// can handle orientation changes and refresh navigation gestural region through
@@ -1276,82 +1288,47 @@
preloadOverview(false /* fromInit */);
}
+ private static boolean isTablet(Configuration config) {
+ return config.smallestScreenWidthDp >= MIN_TABLET_WIDTH;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
- if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
- LinkedList<String> args = new LinkedList(Arrays.asList(rawArgs));
- switch (args.pollFirst()) {
- case "cmd":
- if (args.peekFirst() == null) {
- printAvailableCommands(pw);
- } else {
- onCommand(pw, args);
- }
- break;
- }
- } else {
- // Dump everything
- FlagsFactory.dump(pw);
- if (mDeviceState.isUserUnlocked()) {
- PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
- }
- mDeviceState.dump(pw);
- if (mOverviewComponentObserver != null) {
- mOverviewComponentObserver.dump(pw);
- }
- if (mOverviewCommandHelper != null) {
- mOverviewCommandHelper.dump(pw);
- }
- if (mGestureState != null) {
- mGestureState.dump(pw);
- }
- pw.println("Input state:");
- pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
- pw.println(" mInputEventReceiver=" + mInputEventReceiver);
- DisplayController.INSTANCE.get(this).dump(pw);
- pw.println("TouchState:");
- BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
- : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
- boolean resumed = mOverviewComponentObserver != null
- && mOverviewComponentObserver.getActivityInterface().isResumed();
- pw.println(" createdOverviewActivity=" + createdOverviewActivity);
- pw.println(" resumed=" + resumed);
- pw.println(" mConsumer=" + mConsumer.getName());
- ActiveGestureLog.INSTANCE.dump("", pw);
- RecentsModel.INSTANCE.get(this).dump("", pw);
- pw.println("ProtoTrace:");
- pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
- if (createdOverviewActivity != null) {
- createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
- }
- mTaskbarManager.dumpLogs("", pw);
+ // Dump everything
+ FlagsFactory.dump(pw);
+ if (LockedUserState.get(this).isUserUnlocked()) {
+ PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
}
- }
-
- private void printAvailableCommands(PrintWriter pw) {
- pw.println("Available commands:");
- pw.println(" clear-touch-log: Clears the touch interaction log");
- pw.println(" print-gesture-log: only prints the ActiveGestureLog dump");
- }
-
- private void onCommand(PrintWriter pw, LinkedList<String> args) {
- String cmd = args.pollFirst();
- if (cmd == null) {
- pw.println("Command missing");
- printAvailableCommands(pw);
- return;
+ mDeviceState.dump(pw);
+ if (mOverviewComponentObserver != null) {
+ mOverviewComponentObserver.dump(pw);
}
- switch (cmd) {
- case "clear-touch-log":
- ActiveGestureLog.INSTANCE.clear();
- break;
- case "print-gesture-log":
- ActiveGestureLog.INSTANCE.dump("", pw);
- break;
- default:
- pw.println("Command does not exist: " + cmd);
- printAvailableCommands(pw);
+ if (mOverviewCommandHelper != null) {
+ mOverviewCommandHelper.dump(pw);
}
+ if (mGestureState != null) {
+ mGestureState.dump(pw);
+ }
+ pw.println("Input state:");
+ pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
+ pw.println(" mInputEventReceiver=" + mInputEventReceiver);
+ DisplayController.INSTANCE.get(this).dump(pw);
+ pw.println("TouchState:");
+ BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
+ : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ boolean resumed = mOverviewComponentObserver != null
+ && mOverviewComponentObserver.getActivityInterface().isResumed();
+ pw.println(" createdOverviewActivity=" + createdOverviewActivity);
+ pw.println(" resumed=" + resumed);
+ pw.println(" mConsumer=" + mConsumer.getName());
+ ActiveGestureLog.INSTANCE.dump("", pw);
+ RecentsModel.INSTANCE.get(this).dump("", pw);
+ if (createdOverviewActivity != null) {
+ createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
+ }
+ mTaskbarManager.dumpLogs("", pw);
+ pw.println("AssistStateManager:");
+ AssistStateManager.INSTANCE.get(this).dump(" ", pw);
}
private AbsSwipeUpHandler createLauncherSwipeHandler(
@@ -1367,18 +1344,4 @@
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
mInputConsumer);
}
-
- @Override
- public void writeToProto(LauncherTraceProto.Builder proto) {
- TouchInteractionServiceProto.Builder serviceProto =
- TouchInteractionServiceProto.newBuilder();
- serviceProto.setServiceConnected(true);
-
- if (mOverviewComponentObserver != null) {
- mOverviewComponentObserver.writeToProto(serviceProto);
- }
- mConsumer.writeToProto(serviceProto);
-
- proto.setTouchInteractionService(serviceProto);
- }
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 11b1ab8..8a9e04e 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -15,9 +15,9 @@
*/
package com.android.quickstep.fallback;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 074aedd..95d88cd 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -35,6 +35,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -79,11 +80,16 @@
}
@Override
- public void startHome(boolean animated) {
+ protected void handleStartHome(boolean animated) {
mActivity.startHome();
AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
}
+ @Override
+ protected boolean canStartHomeSafely() {
+ return mActivity.canStartHomeSafely();
+ }
+
/**
* When starting gesture interaction from home, we add a temporary invisible tile corresponding
* to the home task. This allows us to handle quick-switch similarly to a quick-switching
@@ -246,7 +252,11 @@
setOverviewSelectEnabled(false);
}
if (finalState != OVERVIEW_SPLIT_SELECT) {
- resetFromSplitSelectionState();
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.resetState();
+ } else {
+ resetFromSplitSelectionState();
+ }
}
if (isOverlayEnabled) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 6a36d9f..ec6efcb 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -103,8 +103,7 @@
if (mState == STATE_INACTIVE) {
int pointerIndex = ev.getActionIndex();
if (mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev,
- pointerIndex, mGestureState.getActivityInterface())
- && mDelegate.allowInterceptByParent()) {
+ pointerIndex) && mDelegate.allowInterceptByParent()) {
setActive(ev);
mActivePointerId = ev.getPointerId(pointerIndex);
@@ -153,4 +152,9 @@
mDelegate.onMotionEvent(ev);
}
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "AccessibilityInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 162ace4..ba012c9 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -42,9 +42,9 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
@@ -209,7 +209,7 @@
SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(0f);
}
});
- animator.setInterpolator(Interpolators.DEACCEL_2);
+ animator.setInterpolator(Interpolators.DECELERATE_2);
animator.start();
}
mPassedSlop = false;
@@ -278,4 +278,9 @@
return true;
}
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "AssistantInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 03f8eef..63771f0 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -4,8 +4,8 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.InputConsumerProto;
import com.android.quickstep.InputConsumer;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.system.InputMonitorCompat;
public abstract class DelegateInputConsumer implements InputConsumer {
@@ -43,7 +43,15 @@
mDelegate.onConsumerAboutToBeSwitched();
}
+ /**
+ * Returns the name of this DelegateInputConsumer.
+ */
+ protected abstract String getDelegatorName();
+
protected void setActive(MotionEvent ev) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(getDelegatorName())
+ .append(" became active"));
+
mState = STATE_ACTIVE;
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -54,9 +62,4 @@
mDelegate.onMotionEvent(event);
event.recycle();
}
-
- @Override
- public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
- mDelegate.writeToProtoInternal(inputConsumerProto);
- }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 59a9582..2a35584 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -39,9 +39,9 @@
import android.view.RemoteAnimationTarget;
import android.view.VelocityTracker;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
@@ -153,8 +153,7 @@
if (!mThresholdCrossed) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx,
- mGestureState.getActivityInterface())) {
+ if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) {
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
finishTouchTracking(ev);
@@ -204,7 +203,7 @@
// Animate back to fullscreen before finishing
ObjectAnimator animator = mProgress.animateToValue(mProgress.value, 0);
animator.setDuration(100);
- animator.setInterpolator(Interpolators.ACCEL);
+ animator.setInterpolator(Interpolators.ACCELERATE);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
new file mode 100644
index 0000000..7a2b343
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.quickstep.inputconsumers;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+/**
+ * Class for extending nav handle long press behavior
+ */
+public class NavHandleLongPressHandler implements ResourceBasedOverride {
+
+ /** Creates NavHandleLongPressHandler as specified by overrides */
+ public static NavHandleLongPressHandler newInstance(Context context) {
+ return Overrides.getObject(NavHandleLongPressHandler.class, context,
+ R.string.nav_handle_long_press_handler_class);
+ }
+
+ /**
+ * Called when nav handle is long pressed to get the Runnable that should be executed by the
+ * caller to invoke long press behavior. If null is returned that means long press couldn't be
+ * handled.
+ * <p>
+ * A Runnable is returned here to ensure the InputConsumer can call
+ * {@link android.view.InputMonitor#pilferPointers()} before invoking the long press behavior
+ * since pilfering can break the long press behavior.
+ */
+ public @Nullable Runnable getLongPressRunnable() {
+ return null;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
new file mode 100644
index 0000000..addcfb8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -0,0 +1,88 @@
+/*
+ * 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.quickstep.inputconsumers;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Listens for a long press
+ */
+public class NavHandleLongPressInputConsumer extends DelegateInputConsumer {
+
+ private final GestureDetector mLongPressDetector;
+ private final NavHandleLongPressHandler mNavHandleLongPressHandler;
+ private final float mNavHandleWidth;
+ private final float mScreenWidth;
+
+ public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
+ InputMonitorCompat inputMonitor) {
+ super(delegate, inputMonitor);
+ mNavHandleWidth = context.getResources().getDimensionPixelSize(
+ R.dimen.navigation_home_handle_width);
+ mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
+
+ mNavHandleLongPressHandler = NavHandleLongPressHandler.newInstance(context);
+
+ mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
+ @Override
+ public void onLongPress(MotionEvent motionEvent) {
+ if (isInArea(motionEvent.getRawX())) {
+ Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
+ if (longPressRunnable != null) {
+ setActive(motionEvent);
+
+ MAIN_EXECUTOR.post(longPressRunnable);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_NAV_HANDLE_LONG_PRESS | mDelegate.getType();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ mLongPressDetector.onTouchEvent(ev);
+ if (mState != STATE_ACTIVE) {
+ mDelegate.onMotionEvent(ev);
+ }
+ }
+
+ protected boolean isInArea(float x) {
+ float areaFromMiddle = mNavHandleWidth / 2.0f;
+ float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);
+
+ return distFromMiddle < areaFromMiddle;
+ }
+
+ @Override
+ protected String getDelegatorName() {
+ return "NavHandleLongPressInputConsumer";
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index 5387c8a..83b556d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -175,4 +175,9 @@
final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
return angle > ANGLE_MIN && angle < ANGLE_MAX;
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "OneHandedModeInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index f9cd4ee..7e61167 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -28,7 +28,6 @@
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -49,7 +48,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.AbsSwipeUpHandler;
@@ -229,8 +227,7 @@
// Until we detect the gesture, handle events as we receive them
mInputEventReceiver.setBatchingEnabled(false);
- Object traceToken = TraceHelper.INSTANCE.beginSection(DOWN_EVT,
- FLAG_CHECK_FOR_RACE_CONDITIONS);
+ TraceHelper.INSTANCE.beginSection(DOWN_EVT);
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
@@ -241,15 +238,14 @@
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
break;
}
case ACTION_POINTER_DOWN: {
if (!mPassedPilferInputSlop) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx,
- mActivityInterface)) {
+ if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) {
forceCancelGesture(ev);
}
}
@@ -282,7 +278,8 @@
if (!mIsDeferredDownTarget) {
// Normal gesture, ensure we pass the drag slop before we start tracking
// the gesture
- if (Math.abs(displacement) > mTouchSlop) {
+ if (mGestureState.isTrackpadGesture() || Math.abs(displacement)
+ > mTouchSlop) {
mPassedWindowMoveSlop = true;
mStartDisplacement = Math.min(displacement, -mTouchSlop);
}
@@ -291,8 +288,8 @@
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
- boolean passedSlop = squaredHypot(displacementX, displacementY)
- >= mSquaredTouchSlop;
+ boolean passedSlop = mGestureState.isTrackpadGesture() || squaredHypot(
+ displacementX, displacementY) >= mSquaredTouchSlop;
if (!mPassedSlopOnThisGesture && passedSlop) {
mPassedSlopOnThisGesture = true;
@@ -352,7 +349,8 @@
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
}
- if (mDeviceState.isFullyGesturalNavMode()) {
+ if (mDeviceState.isFullyGesturalNavMode()
+ || mGestureState.isTrackpadGesture()) {
boolean minSwipeMet = upDist >= Math.max(mMotionPauseMinDisplacement,
mInteractionHandler.getThresholdToAllowMotionPause());
mInteractionHandler.setCanSlowSwipeGoHome(minSwipeMet);
@@ -417,11 +415,11 @@
* the animation can still be running.
*/
private void finishTouchTracking(MotionEvent ev) {
- Object traceToken = TraceHelper.INSTANCE.beginSection(UP_EVT,
- FLAG_CHECK_FOR_RACE_CONDITIONS);
+ TraceHelper.INSTANCE.beginSection(UP_EVT);
+ boolean isCanceled = ev.getActionMasked() == ACTION_CANCEL;
if (mPassedWindowMoveSlop && mInteractionHandler != null) {
- if (ev.getActionMasked() == ACTION_CANCEL) {
+ if (isCanceled) {
mInteractionHandler.onGestureCancelled();
} else {
mVelocityTracker.computeCurrentVelocity(PX_PER_MS);
@@ -443,8 +441,10 @@
if (mActiveCallbacks != null && mInteractionHandler != null) {
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
// The animation started, but with no movement, in this case, there will be no
- // animateToProgress so we have to manually finish here.
- mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
+ // animateToProgress so we have to manually finish here. In the case of
+ // ACTION_CANCEL, someone else may be doing something so finish synchronously.
+ mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */,
+ isCanceled /* forceFinish */);
} else {
// The animation hasn't started yet, so insert a replacement handler into the
// callbacks which immediately finishes the animation after it starts.
@@ -455,7 +455,7 @@
onInteractionGestureFinished();
}
cleanupAfterGesture();
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
private void cleanupAfterGesture() {
@@ -512,13 +512,6 @@
return !mPassedPilferInputSlop;
}
- @Override
- public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
- if (mInteractionHandler != null) {
- mInteractionHandler.writeToProto(inputConsumerProto);
- }
- }
-
/**
* A listener which just finishes the animation immediately after starting. Replaces
* AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index eac09ad..c9c64b6 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -15,7 +15,7 @@
*/
package com.android.quickstep.inputconsumers;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.touch.BaseSwipeDetector.calculateDuration;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index fbe7fde..28ac65b 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -28,6 +28,7 @@
import android.graphics.Rect;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.InputDevice;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -36,10 +37,13 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarThresholdUtils;
import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
+import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.InputConsumer;
+import com.android.quickstep.OverviewCommandHelper;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
@@ -50,6 +54,7 @@
public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
private final TaskbarActivityContext mTaskbarActivityContext;
+ private final OverviewCommandHelper mOverviewCommandHelper;
private final GestureDetector mLongPressDetector;
private final float mSquaredTouchSlop;
@@ -62,6 +67,7 @@
private final int mTaskbarNavThresholdY;
private final boolean mIsTaskbarAllAppsOpen;
private boolean mHasPassedTaskbarNavThreshold;
+ private boolean mIsInBubbleBarArea;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
@@ -78,16 +84,19 @@
private final @Nullable TransitionCallback mTransitionCallback;
public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
- InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext) {
+ InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
+ OverviewCommandHelper overviewCommandHelper) {
super(delegate, inputMonitor);
mTaskbarActivityContext = taskbarActivityContext;
+ mOverviewCommandHelper = overviewCommandHelper;
// TODO(b/270395798): remove this when cleaning up old Persistent Taskbar code.
mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
mScreenWidth = taskbarActivityContext.getDeviceProfile().widthPx;
Resources res = context.getResources();
mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
- mTaskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_from_nav_threshold);
+ mTaskbarNavThreshold = TaskbarThresholdUtils.getFromNavThreshold(res,
+ taskbarActivityContext.getDeviceProfile());
mTaskbarNavThresholdY = taskbarActivityContext.getDeviceProfile().heightPx
- mTaskbarNavThreshold;
mIsTaskbarAllAppsOpen =
@@ -121,7 +130,11 @@
public void onMotionEvent(MotionEvent ev) {
mLongPressDetector.onTouchEvent(ev);
if (mState != STATE_ACTIVE) {
- mDelegate.onMotionEvent(ev);
+ boolean isStashedTaskbarHovered = isMouseEvent(ev)
+ && isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
+ if (!isStashedTaskbarHovered) {
+ mDelegate.onMotionEvent(ev);
+ }
// Only show the transient task bar if the touch events are on the screen.
if (mTaskbarActivityContext != null && !isTrackpadMotionEvent(ev)) {
@@ -136,7 +149,7 @@
mHasPassedTaskbarNavThreshold = false;
mTaskbarActivityContext.setAutohideSuspendFlag(
FLAG_AUTOHIDE_SUSPEND_TOUCHING, true);
- if (isInArea(x)) {
+ if (isInTaskbarArea(x)) {
if (!mIsTransientTaskbar) {
mLongPressDownX = x;
mLongPressDownY = y;
@@ -145,10 +158,12 @@
mCanceledUnstashHint = false;
}
}
-
if (mTransitionCallback != null && !mIsTaskbarAllAppsOpen) {
mTransitionCallback.onActionDown();
}
+ if (mIsTransientTaskbar && isInBubbleBarArea(x)) {
+ mIsInBubbleBarArea = true;
+ }
break;
case MotionEvent.ACTION_POINTER_UP:
int ptrIdx = ev.getActionIndex();
@@ -185,7 +200,11 @@
if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
mHasPassedTaskbarNavThreshold = true;
- mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+ if (mIsInBubbleBarArea) {
+ mTaskbarActivityContext.onSwipeToOpenBubblebar();
+ } else {
+ mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+ }
}
if (dY < 0) {
@@ -208,21 +227,37 @@
mTransitionCallback.onActionEnd();
}
mHasPassedTaskbarNavThreshold = false;
+ mIsInBubbleBarArea = false;
+ break;
+ case MotionEvent.ACTION_BUTTON_RELEASE:
+ if (isStashedTaskbarHovered) {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+ }
break;
}
}
}
}
- private boolean isInArea(float x) {
+ private boolean isInTaskbarArea(float x) {
float areaFromMiddle = mUnstashArea / 2.0f;
float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);
return distFromMiddle < areaFromMiddle;
}
+ private boolean isInBubbleBarArea(float x) {
+ if (mTaskbarActivityContext != null && mIsTransientTaskbar) {
+ BubbleControllers controllers = mTaskbarActivityContext.getBubbleControllers();
+ if (controllers == null) return false;
+ Rect bubbleBarBounds = controllers.bubbleBarViewController.getBubbleBarBounds();
+ return x >= bubbleBarBounds.left && x <= bubbleBarBounds.right;
+ }
+ return false;
+ }
+
private void onLongPressDetected(MotionEvent motionEvent) {
if (mTaskbarActivityContext != null
- && isInArea(motionEvent.getRawX())
+ && isInTaskbarArea(motionEvent.getRawX())
&& !mIsTransientTaskbar) {
boolean taskBarPressed = mTaskbarActivityContext.onLongPressToUnstashTaskbar();
if (taskBarPressed) {
@@ -255,19 +290,17 @@
private void updateHoveredTaskbarState(int x, int y) {
DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
- mStashedTaskbarHandleBounds.set(
+ mBottomEdgeBounds.set(
(dp.widthPx - (int) mUnstashArea) / 2,
- dp.heightPx - dp.stashedTaskbarHeight,
+ dp.heightPx - mStashedTaskbarBottomEdge,
(int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
dp.heightPx);
- mBottomEdgeBounds.set(mStashedTaskbarHandleBounds);
- mBottomEdgeBounds.top = dp.heightPx - mStashedTaskbarBottomEdge;
if (mBottomEdgeBounds.contains(x, y)) {
// If hovering stashed taskbar and then hover screen bottom edge, unstash it.
mTaskbarActivityContext.onSwipeToUnstashTaskbar();
mIsStashedTaskbarHovered = false;
- } else if (!mStashedTaskbarHandleBounds.contains(x, y)) {
+ } else if (!isStashedTaskbarHovered(x, y)) {
// If exit hovering stashed taskbar, remove hint.
startStashedTaskbarHover(/* isHovered = */ false);
}
@@ -275,18 +308,13 @@
private void updateUnhoveredTaskbarState(int x, int y) {
DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
- mStashedTaskbarHandleBounds.set(
- (dp.widthPx - (int) mUnstashArea) / 2,
- dp.heightPx - dp.stashedTaskbarHeight,
- (int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
- dp.heightPx);
mBottomEdgeBounds.set(
0,
dp.heightPx - mBottomScreenEdge,
dp.widthPx,
dp.heightPx);
- if (mStashedTaskbarHandleBounds.contains(x, y)) {
+ if (isStashedTaskbarHovered(x, y)) {
// If enter hovering stashed taskbar, start hint.
startStashedTaskbarHover(/* isHovered = */ true);
} else if (mBottomEdgeBounds.contains(x, y)) {
@@ -299,4 +327,28 @@
mTaskbarActivityContext.startTaskbarUnstashHint(isHovered, /* forceUnstash = */ true);
mIsStashedTaskbarHovered = isHovered;
}
+
+ private boolean isStashedTaskbarHovered(int x, int y) {
+ if (!mTaskbarActivityContext.isTaskbarStashed()
+ || mTaskbarActivityContext.isTaskbarAllAppsOpen()
+ || !ENABLE_CURSOR_HOVER_STATES.get()) {
+ return false;
+ }
+ DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
+ mStashedTaskbarHandleBounds.set(
+ (dp.widthPx - (int) mUnstashArea) / 2,
+ dp.heightPx - dp.stashedTaskbarHeight,
+ (int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
+ dp.heightPx);
+ return mStashedTaskbarHandleBounds.contains(x, y);
+ }
+
+ private boolean isMouseEvent(MotionEvent event) {
+ return event.getSource() == InputDevice.SOURCE_MOUSE;
+ }
+
+ @Override
+ protected String getDelegatorName() {
+ return "TaskbarUnstashInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/StatusBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
similarity index 88%
rename from quickstep/src/com/android/quickstep/inputconsumers/StatusBarInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
index 898aa86..f3e21e1 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/StatusBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
@@ -27,15 +27,15 @@
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.system.InputMonitorCompat;
-/** Allows the status bar to be pull down for notification shade */
-public class StatusBarInputConsumer extends DelegateInputConsumer {
+/** Allows the status bar to be pull down for notification shade using the trackpad. */
+public class TrackpadStatusBarInputConsumer extends DelegateInputConsumer {
private final SystemUiProxy mSystemUiProxy;
private final float mTouchSlop;
private final PointF mDown = new PointF();
private boolean mHasPassedTouchSlop;
- public StatusBarInputConsumer(Context context, InputConsumer delegate,
+ public TrackpadStatusBarInputConsumer(Context context, InputConsumer delegate,
InputMonitorCompat inputMonitor) {
super(delegate, inputMonitor);
@@ -79,7 +79,12 @@
private void dispatchTouchEvent(MotionEvent ev) {
if (mSystemUiProxy.isActive()) {
- mSystemUiProxy.onStatusBarMotionEvent(ev);
+ mSystemUiProxy.onStatusBarTrackpadEvent(ev);
}
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "TrackpadStatusBarInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 6619dd8..49814df 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -15,10 +15,10 @@
*/
package com.android.quickstep.interaction;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Utilities.mapBoundToRange;
import static com.android.launcher3.Utilities.mapRange;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import android.animation.Animator;
@@ -95,11 +95,10 @@
private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
- private TISBindHelper mTISBindHelper;
- private TISBinder mBinder;
- @Nullable private TaskbarManager mTaskbarManager = null;
-
private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
+
+ private TISBindHelper mTISBindHelper;
+
private BgDrawable mBackground;
private View mRootView;
private float mSwipeUpShift;
@@ -174,7 +173,7 @@
LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
getTheme());
- startBackgroundAnimation();
+ startBackgroundAnimation(dp.isTablet);
}
private void runOnUiHelperThread(Runnable runnable) {
@@ -185,7 +184,7 @@
Executors.UI_HELPER_EXECUTOR.execute(runnable);
}
- private void startBackgroundAnimation() {
+ private void startBackgroundAnimation(boolean forTablet) {
if (!Utilities.ATLEAST_S || mVibrator == null) {
return;
}
@@ -201,7 +200,7 @@
.addPrimitive(supportsThud
? VibrationEffect.Composition.PRIMITIVE_THUD
: VibrationEffect.Composition.PRIMITIVE_TICK,
- /* scale= */ 1.0f,
+ /* scale= */ forTablet ? 1.0f : 0.3f,
/* delay= */ 50)
.compose();
@@ -233,29 +232,30 @@
}
private void setSetupUIVisible(boolean visible) {
- if (mBinder == null || mTaskbarManager == null) return;
- mTaskbarManager.setSetupUIVisible(visible);
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager == null) return;
+ taskbarManager.setSetupUIVisible(visible);
}
@Override
protected void onResume() {
super.onResume();
maybeResumeOrPauseBackgroundAnimation();
- if (mBinder != null) {
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
setSetupUIVisible(true);
- mBinder.setSwipeUpProxy(this::createSwipeUpProxy);
+ binder.setSwipeUpProxy(this::createSwipeUpProxy);
}
}
private void onTISConnected(TISBinder binder) {
- mBinder = binder;
- mTaskbarManager = mBinder.getTaskbarManager();
setSetupUIVisible(isResumed());
- mBinder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
- mBinder.setOverviewTargetChangeListener(mBinder::preloadOverviewForSUWAllSet);
- mBinder.preloadOverviewForSUWAllSet();
- if (mTaskbarManager != null) {
- mLauncherStartAnim = mTaskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
+ binder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
+ binder.setOverviewTargetChangeListener(binder::preloadOverviewForSUWAllSet);
+ binder.preloadOverviewForSUWAllSet();
+ TaskbarManager taskbarManager = binder.getTaskbarManager();
+ if (taskbarManager != null) {
+ mLauncherStartAnim = taskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
}
}
@@ -271,10 +271,11 @@
}
private void clearBinderOverride() {
- if (mBinder != null) {
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
setSetupUIVisible(false);
- mBinder.setSwipeUpProxy(null);
- mBinder.setOverviewTargetChangeListener(null);
+ binder.setSwipeUpProxy(null);
+ binder.setOverviewTargetChangeListener(null);
}
}
@@ -302,7 +303,7 @@
}
private AnimatedFloat createSwipeUpProxy(GestureState state) {
- if (state.getRunningTaskId() != getTaskId()) {
+ if (state.getTopRunningTaskId() != getTaskId()) {
return null;
}
mSwipeProgress.updateValue(0);
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 5d25279..631cff7 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -23,9 +23,9 @@
import android.graphics.PointF;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
import com.android.quickstep.util.LottieAnimationColorUtils;
@@ -154,7 +154,7 @@
@Override
public void onBackGestureAttempted(BackGestureResult result) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
switch (mTutorialType) {
@@ -172,7 +172,7 @@
@Override
public void onBackGestureProgress(float diffx, float diffy, boolean isLeftGesture) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
@@ -183,7 +183,7 @@
/* upperBound = */ 1f,
/* toMin = */ 1f,
/* toMax = */ EXITING_APP_MIN_SIZE_PERCENTAGE,
- Interpolators.DEACCEL);
+ Interpolators.DECELERATE);
// shrink the exiting app as we progress through the back gesture
mExitingAppView.setPivotX(isLeftGesture ? mScreenWidth : 0);
@@ -197,7 +197,7 @@
/* upperBound = */ 1f,
/* toMin = */ 0,
/* toMax = */ mExitingAppMargin,
- Interpolators.DEACCEL)
+ Interpolators.DECELERATE)
* (isLeftGesture ? -1 : 1));
// round the corners of the exiting app as we progress through the back gesture
@@ -241,7 +241,7 @@
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index a16b239..b379baa 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -42,6 +42,12 @@
super(fromTutorialMenu);
}
+ @NonNull
+ @Override
+ TutorialType getDefaultTutorialType() {
+ return TutorialType.BACK_NAVIGATION;
+ }
+
@Nullable
@Override
Integer getEdgeAnimationResId() {
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 8eb4059..a9dcad8 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -40,8 +40,8 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.util.VibratorWrapper;
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 62726a0..d102502 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -53,18 +53,15 @@
static final String KEY_USE_TUTORIAL_MENU = "use_tutorial_menu";
@Nullable private TutorialType[] mTutorialSteps;
- private GestureSandboxFragment mFragment;
+ private GestureSandboxFragment mCurrentFragment;
+ private GestureSandboxFragment mPendingFragment;
private int mCurrentStep;
private int mNumSteps;
- private boolean mShowRotationPrompt;
private SharedPreferences mSharedPrefs;
private StatsLogManager mStatsLogManager;
-
- private View mRotationPrompt;
private TISBindHelper mTISBindHelper;
- private TISBinder mBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -83,7 +80,7 @@
&& args.getBoolean(KEY_USE_TUTORIAL_MENU, false)) {
mTutorialSteps = null;
TutorialType tutorialTypeOverride = (TutorialType) args.get(KEY_TUTORIAL_TYPE);
- mFragment = tutorialTypeOverride == null
+ mCurrentFragment = tutorialTypeOverride == null
? new MenuFragment()
: makeTutorialFragment(
tutorialTypeOverride,
@@ -91,16 +88,15 @@
/* fromMenu= */ true);
} else {
mTutorialSteps = getTutorialSteps(args);
- mFragment = makeTutorialFragment(
+ mCurrentFragment = makeTutorialFragment(
mTutorialSteps[mCurrentStep - 1],
gestureComplete,
/* fromMenu= */ false);
}
getSupportFragmentManager().beginTransaction()
- .add(R.id.gesture_tutorial_fragment_container, mFragment)
+ .add(R.id.gesture_tutorial_fragment_container, mCurrentFragment)
.commit();
- mRotationPrompt = findViewById(R.id.rotation_prompt);
if (FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
correctUserOrientation();
}
@@ -128,34 +124,45 @@
DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.get(
getApplicationContext()).getDeviceProfile(this);
if (deviceProfile.isTablet) {
- mShowRotationPrompt = getResources().getConfiguration().orientation
+ boolean showRotationPrompt = getResources().getConfiguration().orientation
== ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- updateVisibility(mRotationPrompt, mShowRotationPrompt ? View.VISIBLE : View.GONE);
+
+ GestureSandboxFragment recreatedFragment =
+ showRotationPrompt || mPendingFragment == null
+ ? null : mPendingFragment.recreateFragment();
+ showFragment(showRotationPrompt
+ ? new RotationPromptFragment()
+ : recreatedFragment == null
+ ? mCurrentFragment : recreatedFragment);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
- void updateVisibility(View view, int visibility) {
- if (view == null || view.getVisibility() == visibility) {
- return;
+ private void showFragment(@NonNull GestureSandboxFragment fragment) {
+ if (mCurrentFragment.recreateFragment() != null) {
+ mPendingFragment = mCurrentFragment;
}
- view.setVisibility(visibility);
+ mCurrentFragment = fragment;
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.gesture_tutorial_fragment_container, mCurrentFragment)
+ .runOnCommit(() -> mCurrentFragment.onAttachedToWindow())
+ .commit();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
- if (mFragment.shouldDisableSystemGestures()) {
+ if (mCurrentFragment.shouldDisableSystemGestures()) {
disableSystemGestures();
}
- mFragment.onAttachedToWindow();
+ mCurrentFragment.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mFragment.onDetachedFromWindow();
+ mCurrentFragment.onDetachedFromWindow();
}
@Override
@@ -170,14 +177,10 @@
protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
savedInstanceState.putInt(KEY_CURRENT_STEP, mCurrentStep);
- mFragment.onSaveInstanceState(savedInstanceState);
+ mCurrentFragment.onSaveInstanceState(savedInstanceState);
super.onSaveInstanceState(savedInstanceState);
}
- protected boolean isRotationPromptShowing() {
- return mShowRotationPrompt;
- }
-
protected SharedPreferences getSharedPrefs() {
return mSharedPrefs;
}
@@ -206,7 +209,7 @@
*/
public void continueTutorial() {
if (isTutorialComplete() || mTutorialSteps == null) {
- mFragment.close();
+ mCurrentFragment.close();
return;
}
launchTutorialStep(mTutorialSteps[mCurrentStep], false);
@@ -225,20 +228,12 @@
* the menu when complete.
*/
public void launchTutorialStep(@NonNull TutorialType tutorialType, boolean fromMenu) {
- mFragment = makeTutorialFragment(tutorialType, false, fromMenu);
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.gesture_tutorial_fragment_container, mFragment)
- .runOnCommit(() -> mFragment.onAttachedToWindow())
- .commit();
+ showFragment(makeTutorialFragment(tutorialType, false, fromMenu));
}
/** Launches the gesture nav tutorial menu page */
public void launchTutorialMenu() {
- mFragment = new MenuFragment();
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.gesture_tutorial_fragment_container, mFragment)
- .runOnCommit(() -> mFragment.onAttachedToWindow())
- .commit();
+ showFragment(new MenuFragment());
}
private String[] getTutorialStepNames() {
@@ -321,7 +316,6 @@
}
private void onTISConnected(TISBinder binder) {
- mBinder = binder;
updateServiceState(isResumed());
}
@@ -332,8 +326,9 @@
}
private void updateServiceState(boolean isEnabled) {
- if (mBinder != null) {
- mBinder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
+ binder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
index d52f19a..08f2989 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
@@ -17,6 +17,7 @@
import android.app.Activity;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -27,6 +28,11 @@
void onDetachedFromWindow() {}
+ @Nullable
+ GestureSandboxFragment recreateFragment() {
+ return null;
+ }
+
boolean shouldDisableSystemGestures() {
return true;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 0aa80d3..daac99b 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -147,7 +147,7 @@
@Override
public void onBackGestureAttempted(BackGestureResult result) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
switch (mTutorialType) {
@@ -174,7 +174,7 @@
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
switch (mTutorialType) {
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
index bced8c4..3e924d7 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -41,6 +41,12 @@
super(fromTutorialMenu);
}
+ @NonNull
+ @Override
+ TutorialType getDefaultTutorialType() {
+ return TutorialType.HOME_NAVIGATION;
+ }
+
@Nullable
@Override
Integer getEdgeAnimationResId() {
diff --git a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
index 46f79b1..dbf141b 100644
--- a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
@@ -19,7 +19,6 @@
import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_TUTORIAL_TYPE;
import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_USE_TUTORIAL_MENU;
-import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,33 +27,23 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
/** Displays the gesture nav tutorial menu. */
public final class MenuFragment extends GestureSandboxFragment {
- @NonNull private Rect mInsets = new Rect();
-
+ @NonNull
@Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mInsets = InvariantDeviceProfile.INSTANCE.get(getContext())
- .getDeviceProfile(getContext()).getInsets();
+ GestureSandboxFragment recreateFragment() {
+ return new MenuFragment();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- View root = inflater.inflate(
+ final View root = inflater.inflate(
R.layout.gesture_tutorial_step_menu, container, false);
- root.setPadding(
- root.getPaddingLeft() + mInsets.left,
- root.getPaddingTop() + mInsets.top,
- root.getPaddingRight() + mInsets.right,
- root.getPaddingBottom() + mInsets.bottom);
-
root.findViewById(R.id.gesture_tutorial_menu_home_button).setOnClickListener(
v -> launchTutorialStep(TutorialController.TutorialType.HOME_NAVIGATION));
root.findViewById(R.id.gesture_tutorial_menu_back_button).setOnClickListener(
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index 6cee690..c4a2216 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -60,12 +60,11 @@
NavBarGestureHandler(Context context) {
mContext = context;
DisplayController.Info displayInfo = DisplayController.INSTANCE.get(mContext).getInfo();
- final int displayRotation = displayInfo.rotation;
Point currentSize = displayInfo.currentSize;
mDisplaySize.set(currentSize.x, currentSize.y);
mSwipeUpTouchTracker =
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
- new NavBarPosition(NavigationMode.NO_BUTTON, displayRotation),
+ new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
null /*onInterceptTouch*/, this);
mMotionPauseDetector = new MotionPauseDetector(context);
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 454dd17..afdc1e5 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -15,7 +15,7 @@
*/
package com.android.quickstep.interaction;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.app.animation.Interpolators.ACCELERATE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
import android.animation.Animator;
@@ -179,7 +179,7 @@
@Override
public void onBackGestureAttempted(BackGestureResult result) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
switch (mTutorialType) {
@@ -206,7 +206,7 @@
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
switch (mTutorialType) {
@@ -254,7 +254,7 @@
public void animateTaskViewToOverview(boolean animateDelayedSuccessFeedback) {
PendingAnimation anim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCELERATE);
if (animateDelayedSuccessFeedback) {
anim.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
index 01074dd..ee1c460 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -41,6 +41,12 @@
super(fromTutorialMenu);
}
+ @NonNull
+ @Override
+ TutorialType getDefaultTutorialType() {
+ return TutorialType.OVERVIEW_NAVIGATION;
+ }
+
@Nullable
@Override
Integer getEdgeAnimationResId() {
diff --git a/quickstep/src/com/android/quickstep/interaction/RotationPromptFragment.java b/quickstep/src/com/android/quickstep/interaction/RotationPromptFragment.java
new file mode 100644
index 0000000..89af647
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/RotationPromptFragment.java
@@ -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.quickstep.interaction;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+/** Displays the prompt requesting that the user rotates their device. */
+public class RotationPromptFragment extends GestureSandboxFragment {
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.gesture_tutorial_rotation_prompt, container, false);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 558d5dc..87defc5 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -18,7 +18,7 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.app.animation.Interpolators.ACCELERATE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
@@ -171,14 +171,14 @@
PendingAnimation anim = new PendingAnimation(300);
if (toOverviewFirst) {
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCELERATE);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
PendingAnimation fadeAnim =
new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
fadeAnim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCELERATE);
if (resetViews) {
fadeAnim.addListener(mResetTaskView);
}
@@ -213,7 +213,7 @@
});
} else {
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCELERATE);
if (resetViews) {
anim.addListener(mResetTaskView);
}
@@ -239,8 +239,8 @@
mFakeTaskView.setVisibility(View.VISIBLE);
PendingAnimation anim = new PendingAnimation(300);
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
- anim.setViewAlpha(mFakeTaskView, 1, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCELERATE);
+ anim.setViewAlpha(mFakeTaskView, 1, ACCELERATE);
anim.addListener(mResetTaskView);
AnimatorSet animset = anim.buildAnim();
if (animateTaskbar) {
@@ -260,7 +260,7 @@
mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
// After home animation finishes, fade out and run onEndRunnable.
PendingAnimation fadeAnim = new PendingAnimation(300);
- fadeAnim.setViewAlpha(mFakeIconView, 0, ACCEL);
+ fadeAnim.setViewAlpha(mFakeIconView, 0, ACCELERATE);
final View hotseatIconView = mHotseatIconView;
if (hotseatIconView != null) {
hotseatIconView.setVisibility(INVISIBLE);
@@ -282,7 +282,7 @@
@Override
public void setNavBarGestureProgress(@Nullable Float displacement) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
if (mTutorialType == HOME_NAVIGATION_COMPLETE
@@ -303,7 +303,7 @@
@Override
public void onMotionPaused(boolean unused) {
- if (skipGestureAttempt()) {
+ if (isGestureCompleted()) {
return;
}
if (mShowTasks) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 139083b..545a94d 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -487,10 +487,6 @@
return mGestureCompleted;
}
- public boolean skipGestureAttempt() {
- return isGestureCompleted() || mTutorialFragment.isRotationPromptShowing();
- }
-
void hideFeedback() {
if (mFeedbackView.getVisibility() != View.VISIBLE) {
return;
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 84326f5..a28cf58 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -100,6 +100,19 @@
return fragment;
}
+ @Nullable
+ @Override
+ GestureSandboxFragment recreateFragment() {
+ TutorialType tutorialType = mTutorialController == null
+ ? (mTutorialType == null
+ ? getDefaultTutorialType() : mTutorialType)
+ : mTutorialController.mTutorialType;
+ return newInstance(tutorialType, isGestureComplete(), mFromTutorialMenu);
+ }
+
+ @NonNull
+ abstract TutorialType getDefaultTutorialType();
+
TutorialFragment(boolean fromTutorialMenu) {
mFromTutorialMenu = fromTutorialMenu;
}
@@ -498,11 +511,6 @@
return activity != null ? activity.getStatsLogManager() : null;
}
- protected boolean isRotationPromptShowing() {
- GestureSandboxActivity activity = getGestureSandboxActivity();
- return activity != null && activity.isRotationPromptShowing();
- }
-
@Nullable
private SharedPreferences getSharedPreferences() {
GestureSandboxActivity activity = getGestureSandboxActivity();
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 6288937..b1daac4 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -62,14 +62,11 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
import com.android.launcher3.views.ActivityContext;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.SysUiStatsLog;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -90,6 +87,7 @@
private static final String LATENCY_TAG = "StatsLatencyLog";
private static final String IMPRESSION_TAG = "StatsImpressionLog";
private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
+ private static final boolean DEBUG = !Utilities.isRunningInTestHarness();
private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
// LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
// from nano to lite, bake constant to prevent robo test failure.
@@ -110,8 +108,6 @@
public static final CopyOnWriteArrayList<StatsLogConsumer> LOGS_CONSUMER =
new CopyOnWriteArrayList<>();
- private final Context mContext;
-
public StatsLogCompatManager(Context context) {
mContext = context;
}
@@ -331,6 +327,11 @@
if (!Utilities.ATLEAST_R) {
return;
}
+ if (DEBUG) {
+ String name = (event instanceof Enum) ? ((Enum) event).name() :
+ event.getId() + "";
+ Log.d(TAG, name);
+ }
LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
if (mSlice == null && mSliceItem != null) {
@@ -566,14 +567,16 @@
* Helps to construct and log statsd compatible impression events.
*/
private static class StatsCompatImpressionLogger implements StatsImpressionLogger {
- private int[] mResultTypeList = new int[]{};
- private int[] mResultCountList = new int[]{};
- private final List<Boolean> mAboveKeyboardList = new ArrayList<>();
- private int[] mUidList = new int[]{};
private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
private State mLauncherState = State.UNKNOWN;
private int mQueryLength = -1;
+ // Fields used for Impression Logging V2.
+ private int mResultType;
+ private boolean mAboveKeyboard = false;
+ private int mUid;
+ private int mResultSource;
+
@Override
public StatsImpressionLogger withInstanceId(InstanceId instanceId) {
this.mInstanceId = instanceId;
@@ -593,69 +596,60 @@
}
@Override
- public StatsImpressionLogger withResultType(IntArray resultType) {
- mResultTypeList = resultType.toArray();
+ public StatsImpressionLogger withResultType(int resultType) {
+ mResultType = resultType;
+ return this;
+ }
+
+
+ @Override
+ public StatsImpressionLogger withAboveKeyboard(boolean aboveKeyboard) {
+ mAboveKeyboard = aboveKeyboard;
return this;
}
@Override
- public StatsImpressionLogger withResultCount(IntArray resultCount) {
- mResultCountList = resultCount.toArray();
+ public StatsImpressionLogger withUid(int uid) {
+ mUid = uid;
return this;
}
@Override
- public StatsImpressionLogger withAboveKeyboard(List<Boolean> aboveKeyboard) {
- mAboveKeyboardList.clear();
- this.mAboveKeyboardList.addAll(aboveKeyboard);
- return this;
- }
-
- @Override
- public StatsImpressionLogger withUids(IntArray uid) {
- mUidList = uid.toArray();
+ public StatsImpressionLogger withResultSource(int resultSource) {
+ mResultSource = resultSource;
return this;
}
@Override
public void log(EventEnum event) {
- boolean[] mAboveKeyboard = new boolean[mAboveKeyboardList.size()];
- for (int i = 0; i < mAboveKeyboardList.size(); i++) {
- mAboveKeyboard[i] = mAboveKeyboardList.get(i);
- }
if (IS_VERBOSE) {
String name = (event instanceof Enum) ? ((Enum) event).name() :
event.getId() + "";
StringBuilder logStringBuilder = new StringBuilder("\n");
logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId));
logStringBuilder.append(String.format("ImpressionEvent:%s ", name));
- logStringBuilder.append(String.format("LauncherState = %s ", mLauncherState));
- logStringBuilder.append(String.format("QueryLength = %s ", mQueryLength));
- for (int i = 0; i < mResultTypeList.length; i++) {
- logStringBuilder.append(String.format(
- "\n ResultType = %s with ResultCount = %s with is_above_keyboard = %s"
- + " with uid = %s",
- mResultTypeList[i], mResultCountList[i],
- mAboveKeyboard[i], mUidList[i]));
- }
+ logStringBuilder.append(String.format("\n\tLauncherState = %s ", mLauncherState));
+ logStringBuilder.append(String.format("\tQueryLength = %s ", mQueryLength));
+ logStringBuilder.append(String.format(
+ "\n\t ResultType = %s is_above_keyboard = %s"
+ + " uid = %s result_source = %s",
+ mResultType,
+ mAboveKeyboard, mUid, mResultSource));
+
Log.d(IMPRESSION_TAG, logStringBuilder.toString());
}
-
- SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_IMPRESSION_EVENT,
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_IMPRESSION_EVENT_V2,
event.getId(), // event_id
mInstanceId.getId(), // instance_id
mLauncherState.getLauncherState(), // state
mQueryLength, // query_length
- //result type list
- mResultTypeList,
- // result count list
- mResultCountList,
- // above keyboard list
- mAboveKeyboard,
- // uid list
- mUidList
+ mResultType, //result type
+ mAboveKeyboard, // above keyboard
+ mUid, // uid
+ mResultSource // result source
+
);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 409bf9c..cca4d52 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -18,12 +18,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.config.FeatureFlags;
-
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -150,10 +147,6 @@
lastEventEntries.add(eventEntry);
}
- public void clear() {
- Arrays.fill(logs, null);
- }
-
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "ActiveGestureErrorDetector:");
for (int i = 0; i < logs.length; i++) {
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index a92ab2a..cb35ec8 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -15,9 +15,9 @@
*/
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.AbsSwipeUpHandler.ALL_APPS_SHIFT_THRESHOLD;
+import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -34,6 +34,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -55,7 +56,7 @@
private enum RecentsResistanceParams {
FROM_APP(0.75f, 0.5f, 1f, false),
- FROM_APP_TO_ALL_APPS(0.75f, 0.5f, 0.8f, false),
+ FROM_APP_TO_ALL_APPS(1f, 0.6f, 0.8f, false),
FROM_APP_TABLET(1f, 0.7f, 1f, true),
FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
@@ -91,7 +92,7 @@
public final boolean stopScalingAtTop;
}
- private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
+ private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DECELERATE;
private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
private static final Rect TEMP_RECT = new Rect();
@@ -188,7 +189,8 @@
recentsOrientedState.getOrientationHandler());
float dragLengthFactor = (float) dp.heightPx / transitionDragLength;
// -1s are because 0-1 is reserved for the normal transition.
- return (ALL_APPS_SHIFT_THRESHOLD - 1) / (dragLengthFactor - 1);
+ float threshold = LauncherPrefs.get(context).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
+ return (threshold - 1) / (dragLengthFactor - 1);
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index cbde257..1a7099d 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -17,19 +17,30 @@
package com.android.quickstep.util;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.app.ActivityTaskManager;
import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.Arrays;
/**
* Mini controller class that handles app pair interactions: saving, modifying, deleting, etc.
@@ -52,10 +63,13 @@
private final Context mContext;
private final SplitSelectStateController mSplitSelectStateController;
+ private final StatsLogManager mStatsLogManager;
public AppPairsController(Context context,
- SplitSelectStateController splitSelectStateController) {
+ SplitSelectStateController splitSelectStateController,
+ StatsLogManager statsLogManager) {
mContext = context;
mSplitSelectStateController = splitSelectStateController;
+ mStatsLogManager = statsLogManager;
}
/**
@@ -84,11 +98,51 @@
LauncherAccessibilityDelegate delegate =
Launcher.getLauncher(mContext).getAccessibilityDelegate();
if (delegate != null) {
- MAIN_EXECUTOR.execute(() -> delegate.addToWorkspace(newAppPair, true));
+ delegate.addToWorkspace(newAppPair, true);
+ mStatsLogManager.logger().withItemInfo(newAppPair)
+ .log(StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_SAVE);
}
});
});
+ }
+ /**
+ * Launches an app pair by searching the RecentsModel for running instances of each app, and
+ * staging either those running instances or launching the apps as new Intents.
+ */
+ public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
+ ComponentKey app1Key = new ComponentKey(app1.getTargetComponent(), app1.user);
+ ComponentKey app2Key = new ComponentKey(app2.getTargetComponent(), app2.user);
+ mSplitSelectStateController.findLastActiveTasksAndRunCallback(
+ Arrays.asList(app1Key, app2Key),
+ foundTasks -> {
+ @Nullable Task foundTask1 = foundTasks.get(0);
+ Intent task1Intent;
+ int task1Id;
+ if (foundTask1 != null) {
+ task1Id = foundTask1.key.id;
+ task1Intent = null;
+ } else {
+ task1Id = ActivityTaskManager.INVALID_TASK_ID;
+ task1Intent = app1.intent;
+ }
+
+ mSplitSelectStateController.setInitialTaskSelect(task1Intent,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ app1,
+ LAUNCHER_APP_PAIR_LAUNCH,
+ task1Id);
+
+ @Nullable Task foundTask2 = foundTasks.get(1);
+ if (foundTask2 != null) {
+ mSplitSelectStateController.setSecondTask(foundTask2);
+ } else {
+ mSplitSelectStateController.setSecondTask(
+ app2.intent, app2.user);
+ }
+
+ mSplitSelectStateController.launchSplitTasks();
+ });
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
new file mode 100644
index 0000000..e8c1a78
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -0,0 +1,51 @@
+/*
+ * 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.quickstep.util;
+
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.io.PrintWriter;
+
+/** Class to manage Assistant states. */
+public class AssistStateManager implements ResourceBasedOverride {
+
+ public static final MainThreadInitializedObject<AssistStateManager> INSTANCE =
+ forOverride(AssistStateManager.class, R.string.assist_state_manager_class);
+
+ public AssistStateManager() {}
+
+ /** Whether search is available. */
+ public boolean isSearchAvailable() {
+ return false;
+ }
+
+ /** Return {@code true} if the Settings toggle is enabled. */
+ public boolean isSettingsNavHandleEnabled() {
+ return false;
+ }
+
+ /** Return {@code true} if the Settings toggle is enabled. */
+ public boolean isSettingsHomeButtonEnabled() {
+ return false;
+ }
+
+ /** Dump states. */
+ public void dump(String prefix, PrintWriter writer) {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/AssistUtils.java b/quickstep/src/com/android/quickstep/util/AssistUtils.java
new file mode 100644
index 0000000..11b6ea7
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AssistUtils.java
@@ -0,0 +1,45 @@
+/*
+ * 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.quickstep.util;
+
+import android.content.Context;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+/** Utilities to work with Assistant functionality. */
+public class AssistUtils implements ResourceBasedOverride {
+
+ public AssistUtils() {}
+
+ /** Creates AssistUtils as specified by overrides */
+ public static AssistUtils newInstance(Context context) {
+ return Overrides.getObject(AssistUtils.class, context, R.string.assist_utils_class);
+ }
+
+ /** @return Array of AssistUtils.INVOCATION_TYPE_* that we want to handle instead of SysUI. */
+ public int[] getSysUiAssistOverrideInvocationTypes() {
+ return new int[0];
+ }
+
+ /**
+ * @return {@code true} if the override was handled, i.e. an assist surface was shown or the
+ * request should be ignored. {@code false} means the caller should start assist another way.
+ */
+ public boolean tryStartAssistOverride(int invocationType) {
+ return false;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
new file mode 100644
index 0000000..0dee5b3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -0,0 +1,125 @@
+/*
+ * 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.quickstep.util;
+
+import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
+import static android.content.Intent.ACTION_TIME_CHANGED;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.widget.TextClock.ClockEventDelegate;
+
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.SettingsCache.OnChangeListener;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Extension of {@link ClockEventDelegate} to support async event registration
+ */
+public class AsyncClockEventDelegate extends ClockEventDelegate implements OnChangeListener {
+
+ private final Context mContext;
+ private final SimpleBroadcastReceiver mReceiver =
+ new SimpleBroadcastReceiver(this::onClockEventReceived);
+
+ private final ArrayMap<BroadcastReceiver, Handler> mTimeEventReceivers = new ArrayMap<>();
+ private final List<ContentObserver> mFormatObservers = new ArrayList<>();
+ private final Uri mFormatUri = Settings.System.getUriFor(Settings.System.TIME_12_24);
+
+ private boolean mFormatRegistered = false;
+ private boolean mDestroyed = false;
+
+ public AsyncClockEventDelegate(Context context) {
+ super(context);
+ mContext = context;
+
+ UI_HELPER_EXECUTOR.execute(() ->
+ mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED));
+ }
+
+ @Override
+ public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
+ synchronized (mTimeEventReceivers) {
+ mTimeEventReceivers.put(receiver, handler == null ? new Handler() : handler);
+ }
+ }
+
+ @Override
+ public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
+ synchronized (mTimeEventReceivers) {
+ mTimeEventReceivers.remove(receiver);
+ }
+ }
+
+ @Override
+ public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
+ synchronized (mFormatObservers) {
+ if (!mFormatRegistered && !mDestroyed) {
+ SettingsCache.INSTANCE.get(mContext).register(mFormatUri, this);
+ mFormatRegistered = true;
+ }
+ mFormatObservers.add(observer);
+ }
+ }
+
+ @Override
+ public void unregisterFormatChangeObserver(ContentObserver observer) {
+ synchronized (mFormatObservers) {
+ mFormatObservers.remove(observer);
+ }
+ }
+
+ @Override
+ public void onSettingsChanged(boolean isEnabled) {
+ if (mDestroyed) {
+ return;
+ }
+ synchronized (mFormatObservers) {
+ mFormatObservers.forEach(o -> o.dispatchChange(false, mFormatUri));
+ }
+ }
+ @WorkerThread
+ private void onClockEventReceived(Intent intent) {
+ if (mDestroyed) {
+ return;
+ }
+ synchronized (mReceiver) {
+ mTimeEventReceivers.forEach((r, h) -> h.post(() -> r.onReceive(mContext, intent)));
+ }
+ }
+
+ /**
+ * Unregisters all system callbacks and destroys this delegate
+ */
+ public void onDestroy() {
+ mDestroyed = true;
+ SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
+ UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 931e468..99f564c 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -18,6 +18,7 @@
import android.app.WallpaperManager;
import android.os.IBinder;
import android.util.FloatProperty;
+import android.util.Log;
import android.view.AttachedSurfaceControl;
import android.view.SurfaceControl;
@@ -50,6 +51,9 @@
private static final int DEPTH_INDEX_WIDGET = 1;
private static final int DEPTH_INDEX_COUNT = 2;
+ // b/291401432
+ private static final String TAG = "BaseDepthController";
+
protected final Launcher mLauncher;
/** Property to set the depth for state transition. */
public final MultiProperty stateDepth;
@@ -88,7 +92,7 @@
*/
protected boolean mInEarlyWakeUp;
- private boolean mWaitingOnSurfaceValidity;
+ protected boolean mWaitingOnSurfaceValidity;
public BaseDepthController(Launcher activity) {
mLauncher = activity;
@@ -133,9 +137,11 @@
return;
}
if (mSurface == null) {
+ Log.d(TAG, "mSurface is null and mCurrentBlur is: " + mCurrentBlur);
return;
}
if (!mSurface.isValid()) {
+ Log.d(TAG, "mSurface is not valid");
mWaitingOnSurfaceValidity = true;
onInvalidSurface();
return;
@@ -186,6 +192,8 @@
protected void setSurface(SurfaceControl surface) {
if (mSurface != surface || mWaitingOnSurfaceValidity) {
mSurface = surface;
+ Log.d(TAG, "setSurface:\n\tmWaitingOnSurfaceValidity: " + mWaitingOnSurfaceValidity
+ + "\n\tmSurface: " + mSurface);
applyDepthAndBlur();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/BinderTracker.java b/quickstep/src/com/android/quickstep/util/BinderTracker.java
deleted file mode 100644
index cb04e5b..0000000
--- a/quickstep/src/com/android/quickstep/util/BinderTracker.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.util;
-
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Looper;
-import android.util.Log;
-
-import com.android.launcher3.config.FeatureFlags;
-
-/**
- * Utility class to test and check binder calls during development.
- */
-public class BinderTracker {
-
- private static final String TAG = "BinderTracker";
-
- public static void start() {
- if (!FeatureFlags.IS_STUDIO_BUILD) {
- Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
- return;
- }
-
- Binder.setProxyTransactListener(new Tracker());
- }
-
- public static void stop() {
- if (!FeatureFlags.IS_STUDIO_BUILD) {
- Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
- return;
- }
- Binder.setProxyTransactListener(null);
- }
-
- private static class Tracker implements Binder.ProxyTransactListener {
-
- @Override
- public Object onTransactStarted(IBinder iBinder, int code) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- Log.e(TAG, "Binder call on ui thread", new Exception());
- }
- return null;
- }
-
- @Override
- public void onTransactEnded(Object session) { }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
index 011d45c..7563187 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
@@ -29,9 +29,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Px;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.anim.Interpolators;
/**
* Utility class for drawing a rounded-rect border around a view.
diff --git a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
new file mode 100644
index 0000000..59ff81d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.quickstep.util
+
+import android.animation.ValueAnimator
+import android.os.IBinder
+import android.os.RemoteException
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.TransitionInfo
+import com.android.launcher3.anim.AnimatorListeners.forEndCallback
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.util.TransitionUtil
+
+/** Remote animation which fades out the closing targets */
+class FadeOutRemoteTransition : IRemoteTransition.Stub() {
+
+ override fun mergeAnimation(
+ iBinder: IBinder,
+ transitionInfo: TransitionInfo,
+ transaction: Transaction,
+ mergeTarget: IBinder,
+ finishCB: IRemoteTransitionFinishedCallback
+ ) {
+
+ try {
+ finishCB.onTransitionFinished(null, Transaction())
+ } catch (e: RemoteException) {
+ // Ignore
+ }
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startT: Transaction,
+ finishCB: IRemoteTransitionFinishedCallback
+ ) {
+ val anim = ValueAnimator.ofFloat(1f, 0f)
+
+ val closingControls: MutableList<SurfaceControl> = mutableListOf()
+ for (chg in info.changes) {
+ startT.show(chg.leash)
+ if (TransitionUtil.isClosingType(chg.mode)) {
+ closingControls.add(chg.leash)
+ }
+ }
+ startT.apply()
+
+ anim.addUpdateListener {
+ val t = Transaction()
+ closingControls.forEach { t.setAlpha(it, anim.animatedValue as Float) }
+ t.apply()
+ }
+ anim.addListener(
+ forEndCallback(
+ Runnable {
+ val t = Transaction()
+ closingControls.forEach { t.hide(it) }
+ try {
+ finishCB.onTransitionFinished(null, t)
+ } catch (e: RemoteException) {
+ // Ignore
+ }
+ }
+ )
+ )
+
+ Executors.MAIN_EXECUTOR.execute { anim.start() }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index 2be4f0a..9c49647 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -37,6 +37,10 @@
@TaskView.Type
public final int taskViewType;
+ public GroupTask(@NonNull Task task) {
+ this(task, null, null);
+ }
+
public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) {
this(t1, t2, splitBounds, t2 != null ? TaskView.Type.GROUPED : TaskView.Type.SINGLE);
}
diff --git a/quickstep/src/com/android/quickstep/util/LogUtils.kt b/quickstep/src/com/android/quickstep/util/LogUtils.kt
index 23a41f6..e34c4ec 100644
--- a/quickstep/src/com/android/quickstep/util/LogUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/LogUtils.kt
@@ -20,6 +20,11 @@
import com.android.launcher3.logging.InstanceId
object LogUtils {
+ @JvmStatic
+ fun splitFailureMessage(caller: String, reason: String): String {
+ return "($caller) Splitscreen aborted: $reason"
+ }
+
/**
* @return a [Pair] of two InstanceIds but with different types, one that can be used by
* framework (if needing to pass through an intent or such) and one used in Launcher
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index 59c8263..9418512 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -27,25 +27,22 @@
*/
public class NavBarPosition {
+ private final boolean mIsTablet;
private final NavigationMode mMode;
private final int mDisplayRotation;
public NavBarPosition(NavigationMode mode, Info info) {
+ mIsTablet = info.isTablet(info.realBounds);
mMode = mode;
mDisplayRotation = info.rotation;
}
- public NavBarPosition(NavigationMode mode, int displayRotation) {
- mMode = mode;
- mDisplayRotation = displayRotation;
- }
-
public boolean isRightEdge() {
- return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
+ return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90 && !mIsTablet;
}
public boolean isLeftEdge() {
- return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
+ return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270 && !mIsTablet;
}
public float getRotation() {
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 3cec1a4..6d9ecd9 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -22,12 +22,16 @@
import android.animation.AnimatorSet;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
+import java.util.function.Consumer;
+
/**
* Runs an animation from overview to home. Currently, this animation is just a wrapper around the
* normal state transition and may play a {@link WorkspaceRevealAnim} if we're starting from an
@@ -39,14 +43,18 @@
private final Launcher mLauncher;
private final Runnable mOnReachedHome;
+ @Nullable
+ private final Consumer<AnimatorSet> mSplitCancelConsumer;
// Only run mOnReachedHome when both of these are true.
private boolean mIsHomeStaggeredAnimFinished;
private boolean mIsOverviewHidden;
- public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome) {
+ public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome,
+ @Nullable Consumer<AnimatorSet> splitCancelConsumer) {
mLauncher = launcher;
mOnReachedHome = onReachedHome;
+ mSplitCancelConsumer = splitCancelConsumer;
}
/**
@@ -92,6 +100,11 @@
maybeOverviewToHomeAnimComplete();
}
});
+
+ if (mSplitCancelConsumer != null) {
+ // Clear split state when swiping to home
+ mSplitCancelConsumer.accept(anim);
+ }
anim.play(stateAnim);
stateManager.setCurrentAnimation(anim, NORMAL);
anim.start();
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
index e189a66..3027f79 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
@@ -16,8 +16,8 @@
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.INSTANT;
import android.view.animation.Interpolator;
diff --git a/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java
index f1dde53..a38f437 100644
--- a/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java
+++ b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java
@@ -16,7 +16,7 @@
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import android.view.animation.Interpolator;
diff --git a/quickstep/src/com/android/quickstep/util/ProtoTracer.java b/quickstep/src/com/android/quickstep/util/ProtoTracer.java
deleted file mode 100644
index ef9586d..0000000
--- a/quickstep/src/com/android/quickstep/util/ProtoTracer.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.util;
-
-import static com.android.launcher3.tracing.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_H_VALUE;
-import static com.android.launcher3.tracing.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_L_VALUE;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import android.os.Trace;
-import com.android.launcher3.tracing.LauncherTraceProto;
-import com.android.launcher3.tracing.LauncherTraceEntryProto;
-import com.android.launcher3.tracing.LauncherTraceFileProto;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.tracing.FrameProtoTracer;
-import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
-import com.android.systemui.shared.tracing.ProtoTraceable;
-import com.google.protobuf.MessageLite;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Queue;
-
-
-/**
- * Controller for coordinating winscope proto tracing.
- */
-public class ProtoTracer implements ProtoTraceParams<MessageLite.Builder,
- LauncherTraceFileProto.Builder, LauncherTraceEntryProto.Builder,
- LauncherTraceProto.Builder> {
-
- public static final MainThreadInitializedObject<ProtoTracer> INSTANCE =
- new MainThreadInitializedObject<>(ProtoTracer::new);
-
- private static final String TAG = "ProtoTracer";
- private static final long MAGIC_NUMBER_VALUE =
- ((long) MAGIC_NUMBER_H_VALUE << 32) | MAGIC_NUMBER_L_VALUE;
-
- private final Context mContext;
- private final FrameProtoTracer<MessageLite.Builder, LauncherTraceFileProto.Builder,
- LauncherTraceEntryProto.Builder, LauncherTraceProto.Builder> mProtoTracer;
-
- public ProtoTracer(Context context) {
- mContext = context;
- mProtoTracer = new FrameProtoTracer<>(this);
- }
-
- @Override
- public File getTraceFile() {
- return new File(mContext.getFilesDir(), "launcher_trace.pb");
- }
-
- @Override
- public LauncherTraceFileProto.Builder getEncapsulatingTraceProto() {
- return LauncherTraceFileProto.newBuilder();
- }
-
- @Override
- public LauncherTraceEntryProto.Builder updateBufferProto(
- LauncherTraceEntryProto.Builder reuseObj,
- ArrayList<ProtoTraceable<LauncherTraceProto.Builder>> traceables) {
- Trace.beginSection("ProtoTracer.updateBufferProto");
- LauncherTraceEntryProto.Builder proto = LauncherTraceEntryProto.newBuilder();
- proto.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
- LauncherTraceProto.Builder launcherProto = LauncherTraceProto.newBuilder();
- for (ProtoTraceable t : traceables) {
- t.writeToProto(launcherProto);
- }
- proto.setLauncher(launcherProto);
- Trace.endSection();
- return proto;
- }
-
- @Override
- public byte[] serializeEncapsulatingProto(LauncherTraceFileProto.Builder encapsulatingProto,
- Queue<LauncherTraceEntryProto.Builder> buffer) {
- Trace.beginSection("ProtoTracer.serializeEncapsulatingProto");
- encapsulatingProto.setMagicNumber(MAGIC_NUMBER_VALUE);
- for (LauncherTraceEntryProto.Builder entry : buffer) {
- encapsulatingProto.addEntry(entry);
- }
- byte[] bytes = encapsulatingProto.build().toByteArray();
- Trace.endSection();
- return bytes;
- }
-
- @Override
- public byte[] getProtoBytes(MessageLite.Builder proto) {
- return proto.build().toByteArray();
- }
-
- @Override
- public int getProtoSize(MessageLite.Builder proto) {
- return proto.build().getSerializedSize();
- }
-
- public void start() {
- mProtoTracer.start();
- }
-
- public void stop() {
- mProtoTracer.stop();
- }
-
- public void add(ProtoTraceable<LauncherTraceProto.Builder> traceable) {
- mProtoTracer.add(traceable);
- }
-
- public void remove(ProtoTraceable<LauncherTraceProto.Builder> traceable) {
- mProtoTracer.remove(traceable);
- }
-
- public void scheduleFrameUpdate() {
- mProtoTracer.scheduleFrameUpdate();
- }
-
- public void update() {
- mProtoTracer.update();
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
deleted file mode 100644
index 8f79ccf..0000000
--- a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.unfold.updates.screen.ScreenStatusProvider;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Screen status provider implementation that exposes methods to provide screen
- * status updates to listeners. It is used to receive screen turned on event from
- * SystemUI to Launcher.
- */
-public class ProxyScreenStatusProvider implements ScreenStatusProvider {
-
- public static final ProxyScreenStatusProvider INSTANCE = new ProxyScreenStatusProvider();
- private final List<ScreenListener> mListeners = new ArrayList<>();
-
- /**
- * Called when the screen is on and ready (windows are drawn and screen blocker is removed)
- */
- public void onScreenTurnedOn() {
- mListeners.forEach(ScreenListener::onScreenTurnedOn);
- }
-
- /** Called when the screen is starting to turn on. */
- public void onScreenTurningOn() {
- mListeners.forEach(ScreenListener::onScreenTurningOn);
- }
-
- /** Called when the screen is starting to turn off. */
- public void onScreenTurningOff() {
- mListeners.forEach(ScreenListener::onScreenTurningOff);
- }
-
- @Override
- public void addCallback(@NonNull ScreenListener listener) {
- mListeners.add(listener);
- }
-
- @Override
- public void removeCallback(@NonNull ScreenListener listener) {
- mListeners.remove(listener);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
deleted file mode 100644
index 10f2eaa..0000000
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import android.animation.AnimatorSet;
-import android.view.RemoteAnimationTarget;
-
-public abstract class RemoteAnimationProvider {
-
- public abstract AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
- RemoteAnimationTarget[] wallpaperTargets);
-
- /**
- * @return the target with the lowest opaque layer for a certain app animation, or null.
- */
- public static RemoteAnimationTarget findLowestOpaqueLayerTarget(
- RemoteAnimationTarget[] appTargets, int mode) {
- int lowestLayer = Integer.MAX_VALUE;
- int lowestLayerIndex = -1;
- for (int i = appTargets.length - 1; i >= 0; i--) {
- RemoteAnimationTarget target = appTargets[i];
- if (target.mode == mode && !target.isTranslucent) {
- int layer = target.prefixOrderIndex;
- if (layer < lowestLayer) {
- lowestLayer = layer;
- lowestLayerIndex = i;
- }
- }
- }
- return lowestLayerIndex != -1
- ? appTargets[lowestLayerIndex]
- : null;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
deleted file mode 100644
index 382cf79..0000000
--- a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl.Transaction;
-
-import com.android.quickstep.RemoteAnimationTargets;
-
-/**
- * Animation listener which fades out the closing targets
- */
-public class RemoteFadeOutAnimationListener implements AnimatorUpdateListener {
-
- private final RemoteAnimationTargets mTarget;
- private boolean mFirstFrame = true;
-
- public RemoteFadeOutAnimationListener(RemoteAnimationTarget[] appTargets,
- RemoteAnimationTarget[] wallpaperTargets) {
- mTarget = new RemoteAnimationTargets(appTargets, wallpaperTargets,
- new RemoteAnimationTarget[0], MODE_CLOSING);
- }
-
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- Transaction t = new Transaction();
- if (mFirstFrame) {
- for (RemoteAnimationTarget target : mTarget.unfilteredApps) {
- t.show(target.leash);
- }
- mFirstFrame = false;
- }
-
- float alpha = 1 - valueAnimator.getAnimatedFraction();
- for (RemoteAnimationTarget app : mTarget.apps) {
- t.setAlpha(app.leash, alpha);
- }
- t.apply();
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index b76fe5c..c3774eb 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -17,14 +17,26 @@
package com.android.quickstep.util
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.view.View
+import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Utilities
import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.statemanager.StatefulActivity
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.views.FloatingTaskView
import com.android.quickstep.views.IconView
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.SplitInstructionsView
import com.android.quickstep.views.TaskThumbnailView
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
@@ -176,4 +188,91 @@
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
}
}
+
+ /** Does not play any animation if user is not currently in split selection state. */
+ fun playPlaceholderDismissAnim(launcher: StatefulActivity<*>) {
+ if (!splitSelectStateController.isSplitSelectActive) {
+ return
+ }
+
+ val anim = createPlaceholderDismissAnim(launcher)
+ anim.start()
+ }
+
+ /** Returns [AnimatorSet] which slides initial split placeholder view offscreen. */
+ fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>) : AnimatorSet {
+ val animatorSet = AnimatorSet()
+ val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
+ val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
+ ?: return animatorSet
+
+ // We are in split selection state currently, transitioning to another state
+ val dragLayer: BaseDragLayer<*> = launcher.dragLayer
+ val onScreenRectF = RectF()
+ Utilities.getBoundsForViewInDragLayer(dragLayer, floatingTask,
+ Rect(0, 0, floatingTask.width, floatingTask.height),
+ false, null, onScreenRectF)
+ // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
+ // on-screen portion)
+ onScreenRectF.intersect(
+ dragLayer.left.toFloat(),
+ dragLayer.top.toFloat(),
+ dragLayer.right.toFloat(),
+ dragLayer.bottom
+ .toFloat()
+ )
+ animatorSet.play(ObjectAnimator.ofFloat(floatingTask,
+ FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN,
+ recentsView.pagedOrientationHandler
+ .getFloatingTaskOffscreenTranslationTarget(
+ floatingTask,
+ onScreenRectF,
+ floatingTask.stagePosition,
+ launcher.deviceProfile
+ )))
+ animatorSet.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ splitSelectStateController.resetState()
+ safeRemoveViewFromDragLayer(launcher,
+ splitSelectStateController.splitInstructionsView)
+ }
+ })
+ return animatorSet
+ }
+
+ /**
+ * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second
+ * app for splitscreen
+ */
+ fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>) : PendingAnimation {
+ safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView)
+ val splitInstructionsView = SplitInstructionsView.getSplitInstructionsView(launcher)
+ splitSelectStateController.splitInstructionsView = splitInstructionsView
+ val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet)
+ val anim = PendingAnimation(100 /*duration */)
+ anim.setViewAlpha(splitInstructionsView, 1f,
+ Interpolators.clampToProgress(Interpolators.LINEAR,
+ timings.instructionsContainerFadeInStartOffset,
+ timings.instructionsContainerFadeInEndOffset))
+ anim.setViewAlpha(splitInstructionsView!!.textView, 1f,
+ Interpolators.clampToProgress(Interpolators.LINEAR,
+ timings.instructionsTextFadeInStartOffset,
+ timings.instructionsTextFadeInEndOffset))
+ anim.addFloat(splitInstructionsView, SplitInstructionsView.UNFOLD, 0.1f, 1f,
+ Interpolators.clampToProgress(Interpolators.EMPHASIZED_DECELERATE,
+ timings.instructionsUnfoldStartOffset,
+ timings.instructionsUnfoldEndOffset))
+ return anim
+ }
+
+ /** Removes the split instructions view from [launcher] drag layer. */
+ fun removeSplitInstructionsView(launcher: StatefulActivity<*>) {
+ safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView)
+ }
+
+ private fun safeRemoveViewFromDragLayer(launcher: StatefulActivity<*>, view: View?) {
+ if (view != null) {
+ launcher.dragLayer.removeView(view)
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
index 7dc1b32..93f2255 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -16,7 +16,7 @@
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
import android.view.animation.Interpolator;
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index e073264..ae500a1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -45,7 +45,11 @@
*
* After setting the correct fields for initial/second.* variables, this converts them into the
* correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary
- * data back via [getSplitLaunchData].
+ * data back via [getSplitLaunchData]. Note: there should be only one "initial" field and one
+ * "second" field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are
+ * always passed in together as a set, and are converted to a single [PendingIntent] or
+ * [ShortcutInfo]+[PendingIntent] before launch.)
+ *
* [SplitLaunchType] indicates the type of tasks/apps/intents being launched given the provided
* state
*/
@@ -80,21 +84,21 @@
const val SPLIT_SINGLE_SHORTCUT_FULLSCREEN = 8
}
-
@StagePosition
private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
- private var initialTaskId: Int = INVALID_TASK_ID
- private var secondTaskId: Int = INVALID_TASK_ID
- private var initialUser: UserHandle? = null
- private var secondUser: UserHandle? = null
- private var initialIntent: Intent? = null
- private var secondIntent: Intent? = null
- private var secondPendingIntent: PendingIntent? = null
private var itemInfo: ItemInfo? = null
private var splitEvent: EventEnum? = null
+
+ private var initialTaskId: Int = INVALID_TASK_ID
+ private var secondTaskId: Int = INVALID_TASK_ID
+ private var initialIntent: Intent? = null
+ private var secondIntent: Intent? = null
+ private var initialUser: UserHandle? = null
+ private var secondUser: UserHandle? = null
+ private var initialPendingIntent: PendingIntent? = null
+ private var secondPendingIntent: PendingIntent? = null
private var initialShortcut: ShortcutInfo? = null
private var secondShortcut: ShortcutInfo? = null
- private var initialPendingIntent: PendingIntent? = null
/**
* @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID]
@@ -217,7 +221,7 @@
* split task in fullscreen
*/
fun getFullscreenLaunchData() : SplitLaunchData {
- // Convert all intents to shortcut infos to see if determine if we launch shortcut or intent
+ // Convert all intents to shortcut infos to determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getFullscreenLaunchType()
@@ -370,6 +374,18 @@
return secondTaskId
}
+ fun getSplitEvent(): EventEnum? {
+ return splitEvent
+ }
+
+ fun getInitialStagePosition(): Int {
+ return initialStagePosition
+ }
+
+ fun getItemInfo(): ItemInfo? {
+ return itemInfo
+ }
+
private fun isSecondTaskIntentSet(): Boolean {
return secondTaskId != INVALID_TASK_ID || secondIntent != null
|| secondPendingIntent != null
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index f25619d..d4ddf76 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -16,14 +16,16 @@
package com.android.quickstep.util;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT;
-import static android.app.PendingIntent.FLAG_MUTABLE;
-
import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM;
+import static com.android.launcher3.testing.shared.TestProtocol.LAUNCH_SPLIT_PAIR;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
-import static com.android.launcher3.util.SplitConfigurationOptions.getOppositeStagePosition;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_TASK;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SHORTCUT_TASK;
@@ -34,6 +36,8 @@
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -43,10 +47,14 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -61,28 +69,42 @@
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SplitSelectionListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.GroupedTaskView;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.SplitInstructionsView;
+import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -92,36 +114,18 @@
public class SplitSelectStateController {
private static final String TAG = "SplitSelectStateCtor";
- private final Context mContext;
+ private Context mContext;
private final Handler mHandler;
private final RecentsModel mRecentTasksModel;
private final SplitAnimationController mSplitAnimationController;
private final AppPairsController mAppPairsController;
private final SplitSelectDataHolder mSplitSelectDataHolder;
- private StatsLogManager mStatsLogManager;
+ private final StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
+ private SplitFromDesktopController mSplitFromDesktopController;
@Nullable
private DepthController mDepthController;
- private @StagePosition int mInitialStagePosition;
- private ItemInfo mItemInfo;
- /** {@link #mInitialTaskIntent} and {@link #mInitialUser} (the user of the Intent) are set
- * together when split is initiated from an Intent. */
- private Intent mInitialTaskIntent;
- private UserHandle mInitialUser;
- private int mInitialTaskId = INVALID_TASK_ID;
- /** {@link #mSecondTaskIntent} and {@link #mSecondUser} (the user of the Intent) are set
- * together when split is confirmed with an Intent. Either this or {@link #mSecondPendingIntent}
- * will be set, but not both
- */
- private Intent mSecondTaskIntent;
- /**
- * Set when split is confirmed via a widget. Either this or {@link #mSecondTaskIntent} will be
- * set, but not both
- */
- private PendingIntent mSecondPendingIntent;
- private UserHandle mSecondUser;
- private int mSecondTaskId = INVALID_TASK_ID;
private boolean mRecentsAnimationRunning;
/** If {@code true}, animates the existing task view split placeholder view */
private boolean mAnimateCurrentTaskDismissal;
@@ -133,10 +137,11 @@
/** If not null, this is the TaskView we want to launch from */
@Nullable
private GroupedTaskView mLaunchingTaskView;
- /** Represents where split is intended to be invoked from. */
- private StatsLogManager.EventEnum mSplitEvent;
private FloatingTaskView mFirstFloatingTaskView;
+ private SplitInstructionsView mSplitInstructionsView;
+
+ private final List<SplitSelectionListener> mSplitSelectionListeners = new ArrayList<>();
public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
DepthController depthController, StatsLogManager statsLogManager,
@@ -149,10 +154,14 @@
mDepthController = depthController;
mRecentTasksModel = recentsModel;
mSplitAnimationController = new SplitAnimationController(this);
- mAppPairsController = new AppPairsController(context, this);
+ mAppPairsController = new AppPairsController(context, this, statsLogManager);
mSplitSelectDataHolder = new SplitSelectDataHolder(mContext);
}
+ public void onDestroy() {
+ mContext = null;
+ }
+
/**
* @param alreadyRunningTask if set to {@link android.app.ActivityTaskManager#INVALID_TASK_ID}
* then @param intent will be used to launch the initial task
@@ -161,19 +170,8 @@
public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition,
@NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent,
int alreadyRunningTask) {
- if (alreadyRunningTask != INVALID_TASK_ID) {
- mInitialTaskId = alreadyRunningTask;
- } else {
- mInitialTaskIntent = intent;
- mInitialUser = itemInfo.user;
- }
-
- setInitialData(stagePosition, splitEvent, itemInfo);
-
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent,
- alreadyRunningTask);
- }
+ mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent,
+ alreadyRunningTask);
}
/**
@@ -183,53 +181,50 @@
public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info,
@StagePosition int stagePosition, @NonNull ItemInfo itemInfo,
StatsLogManager.EventEnum splitEvent) {
- mInitialTaskId = info.taskId;
- setInitialData(stagePosition, splitEvent, itemInfo);
-
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent);
- }
- }
-
- private void setInitialData(@StagePosition int stagePosition,
- StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
- mItemInfo = itemInfo;
- mInitialStagePosition = stagePosition;
- mSplitEvent = splitEvent;
+ mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent);
}
/**
- * Pulls the list of active Tasks from RecentsModel, and finds the most recently active Task
- * matching a given ComponentName. Then uses that Task (which could be null) with the given
- * callback.
+ * Maps a List<ComponentKey> to List<@Nullable Task>, searching through active Tasks in
+ * RecentsModel. If found, the Task will be the most recently-interacted-with instance of that
+ * Task. Then runs the given callback on that List.
* <p>
* Used in various task-switching or splitscreen operations when we need to check if there is a
* currently running Task of a certain type and use the most recent one.
*/
- public void findLastActiveTaskAndRunCallback(
- @Nullable ComponentKey componentKey, Consumer<Task> callback) {
+ public void findLastActiveTasksAndRunCallback(
+ @Nullable List<ComponentKey> componentKeys, Consumer<List<Task>> callback) {
mRecentTasksModel.getTasks(taskGroups -> {
- if (componentKey == null) {
- callback.accept(null);
+ if (componentKeys == null || componentKeys.isEmpty()) {
+ callback.accept(Collections.emptyList());
return;
}
- Task lastActiveTask = null;
- // Loop through tasks in reverse, since they are ordered with most-recent tasks last.
- for (int i = taskGroups.size() - 1; i >= 0; i--) {
- GroupTask groupTask = taskGroups.get(i);
- Task task1 = groupTask.task1;
- if (isInstanceOfComponent(task1, componentKey)) {
- lastActiveTask = task1;
- break;
+
+ List<Task> lastActiveTasks = new ArrayList<>();
+ // For each key we are looking for, add to lastActiveTasks with the corresponding Task
+ // (or null if not found).
+ for (ComponentKey key : componentKeys) {
+ Task lastActiveTask = null;
+ // Loop through tasks in reverse, since they are ordered with most-recent tasks last
+ for (int i = taskGroups.size() - 1; i >= 0; i--) {
+ GroupTask groupTask = taskGroups.get(i);
+ Task task1 = groupTask.task1;
+ // Don't add duplicate Tasks
+ if (isInstanceOfComponent(task1, key) && !lastActiveTasks.contains(task1)) {
+ lastActiveTask = task1;
+ break;
+ }
+ Task task2 = groupTask.task2;
+ if (isInstanceOfComponent(task2, key) && !lastActiveTasks.contains(task2)) {
+ lastActiveTask = task2;
+ break;
+ }
}
- Task task2 = groupTask.task2;
- if (isInstanceOfComponent(task2, componentKey)) {
- lastActiveTask = task2;
- break;
- }
+
+ lastActiveTasks.add(lastActiveTask);
}
- callback.accept(lastActiveTask);
+ callback.accept(lastActiveTasks);
});
}
@@ -239,7 +234,7 @@
*/
public boolean isInstanceOfComponent(@Nullable Task task, @NonNull ComponentKey componentKey) {
// Exclude the task that is already staged
- if (task == null || task.key.id == mInitialTaskId) {
+ if (task == null || task.key.id == mSplitSelectDataHolder.getInitialTaskId()) {
return false;
}
@@ -248,20 +243,48 @@
}
/**
- * To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are
- * to be launched. Call after launcher side animations are complete.
+ * Listener will only get callbacks going forward from the point of registration. No
+ * methods will be fired upon registering.
*/
- public void launchSplitTasks(Consumer<Boolean> callback) {
+ public void registerSplitListener(@NonNull SplitSelectionListener listener) {
+ if (mSplitSelectionListeners.contains(listener)) {
+ return;
+ }
+ mSplitSelectionListeners.add(listener);
+ }
+
+ public void unregisterSplitListener(@NonNull SplitSelectionListener listener) {
+ mSplitSelectionListeners.remove(listener);
+ }
+
+ private void dispatchOnSplitSelectionExit() {
+ for (SplitSelectionListener listener : mSplitSelectionListeners) {
+ listener.onSplitSelectionExit(false);
+ }
+ }
+
+ /**
+ * To be called when the both split tasks are ready to be launched. Call after launcher side
+ * animations are complete.
+ */
+ public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
LogUtils.getShellShareableInstanceId();
- launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
- mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
+ launchTasks(callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
instanceIds.first);
mStatsLogManager.logger()
- .withItemInfo(mItemInfo)
+ .withItemInfo(mSplitSelectDataHolder.getItemInfo())
.withInstanceId(instanceIds.second)
- .log(mSplitEvent);
+ .log(mSplitSelectDataHolder.getSplitEvent());
+ }
+
+ /**
+ * A version of {@link #launchTasks(Consumer, boolean, float, InstanceId)} with no success
+ * callback.
+ */
+ public void launchSplitTasks() {
+ launchSplitTasks(null);
}
/**
@@ -269,11 +292,7 @@
* @param task The second task that will be launched.
*/
public void setSecondTask(Task task) {
- mSecondTaskId = task.key.id;
-
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- mSplitSelectDataHolder.setSecondTask(task.key.id);
- }
+ mSplitSelectDataHolder.setSecondTask(task.key.id);
}
/**
@@ -282,108 +301,29 @@
* @param user The user of that intent.
*/
public void setSecondTask(Intent intent, UserHandle user) {
- mSecondTaskIntent = intent;
- mSecondUser = user;
-
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- mSplitSelectDataHolder.setSecondTask(intent, user);
- }
+ mSplitSelectDataHolder.setSecondTask(intent, user);
}
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
- * Sets {@link #mSecondUser} from that of the pendingIntent
* @param pendingIntent The second PendingIntent that will be launched.
*/
public void setSecondTask(PendingIntent pendingIntent) {
- mSecondPendingIntent = pendingIntent;
- mSecondUser = pendingIntent.getCreatorUserHandle();
-
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- mSplitSelectDataHolder.setSecondTask(pendingIntent);
- }
+ mSplitSelectDataHolder.setSecondTask(pendingIntent);
}
/**
* To be called when we want to launch split pairs from Overview. Split can be initiated from
* either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
* fill in intent with a taskId2 are set.
- * @param intent1 is null when split is initiated from Overview
- * @param stagePosition representing location of task1
* @param shellInstanceId loggingId to be used by shell, will be non-null for actions that
* create a split instance, null for cases that bring existing instaces to the
* foreground (quickswitch, launching previous pairs from overview)
*/
- public void launchTasks(int taskId1, @Nullable Intent intent1, int taskId2,
- @Nullable Intent intent2, @StagePosition int stagePosition,
- Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
- @Nullable InstanceId shellInstanceId) {
+ public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList,
+ float splitRatio, @Nullable InstanceId shellInstanceId) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- launchTasksRefactored(callback, freezeTaskList, splitRatio, shellInstanceId);
- return;
- }
-
- final ActivityOptions options1 = ActivityOptions.makeBasic();
- if (freezeTaskList) {
- options1.setFreezeRecentTasksReordering();
- }
- boolean hasSecondaryPendingIntent = mSecondPendingIntent != null;
- if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
- final RemoteTransition remoteTransition = getShellRemoteTransition(taskId1, taskId2,
- callback);
- if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
- mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
- null /* options2 */, stagePosition, splitRatio, remoteTransition,
- shellInstanceId);
- } else if (intent2 == null && !hasSecondaryPendingIntent) {
- launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition,
- splitRatio, remoteTransition, shellInstanceId);
- } else if (intent1 == null) {
- launchIntentOrShortcut(intent2, mSecondUser, options1, taskId1,
- getOppositeStagePosition(stagePosition), splitRatio, remoteTransition,
- shellInstanceId);
- } else {
- mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
- mInitialUser.getIdentifier(), getShortcutInfo(intent1, mInitialUser),
- options1.toBundle(), hasSecondaryPendingIntent
- ? mSecondPendingIntent
- : getPendingIntent(intent2, mSecondUser),
- mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
- null /* options2 */, stagePosition, splitRatio, remoteTransition,
- shellInstanceId);
- }
- } else {
- final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(taskId1, taskId2,
- callback);
-
- if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
- mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
- taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
- shellInstanceId);
- } else if (intent2 == null && !hasSecondaryPendingIntent) {
- launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2,
- stagePosition, splitRatio, adapter, shellInstanceId);
- } else if (intent1 == null) {
- launchIntentOrShortcutLegacy(intent2, mSecondUser, options1, taskId1,
- getOppositeStagePosition(stagePosition), splitRatio, adapter,
- shellInstanceId);
- } else {
- mSystemUiProxy.startIntentsWithLegacyTransition(
- getPendingIntent(intent1, mInitialUser), mInitialUser.getIdentifier(),
- getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
- hasSecondaryPendingIntent
- ? mSecondPendingIntent
- : getPendingIntent(intent2, mSecondUser),
- mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
- null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId);
- }
- }
- }
-
- private void launchTasksRefactored(Consumer<Boolean> callback, boolean freezeTaskList,
- float splitRatio, @Nullable InstanceId shellInstanceId) {
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
options1.setFreezeRecentTasksReordering();
@@ -404,7 +344,7 @@
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
- secondTaskId, callback);
+ secondTaskId, callback, "LaunchSplitPair");
switch (launchData.getSplitLaunchType()) {
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
@@ -477,9 +417,8 @@
/**
* Used to launch split screen from a split pair that already exists (usually accessible through
- * Overview). This is different than
- * {@link #launchTasks(int, Intent, int, Intent, int, Consumer, boolean, float, InstanceId)} in
- * that this only launches split screen that are existing tasks. This doesn't determine which
+ * Overview). This is different than {@link #launchTasks(Consumer, boolean, float, InstanceId)}
+ * in that this only launches split screen that are existing tasks. This doesn't determine which
* API should be used (i.e. launching split with existing tasks vs intents vs shortcuts, etc).
*
* <p/>
@@ -497,7 +436,7 @@
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
- secondTaskId, callback);
+ secondTaskId, callback, "LaunchExistingPair");
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
null /* options2 */, stagePosition, splitRatio,
remoteTransition, null /*shellInstanceId*/);
@@ -516,11 +455,6 @@
* split and fullscreen tasks)
*/
public void launchInitialAppFullscreen(Consumer<Boolean> callback) {
- if (!FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- launchSplitTasks(callback);
- return;
- }
-
final ActivityOptions options1 = ActivityOptions.makeBasic();
SplitSelectDataHolder.SplitLaunchData launchData =
mSplitSelectDataHolder.getFullscreenLaunchData();
@@ -536,7 +470,7 @@
new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
ActivityThread.currentActivityThread().getApplicationThread(),
- "LaunchSplitPair");
+ "LaunchAppFullscreen");
InstanceId instanceId = LogUtils.getShellShareableInstanceId().first;
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
switch (launchData.getSplitLaunchType()) {
@@ -571,111 +505,38 @@
}
}
+ public void initSplitFromDesktopController(Launcher launcher) {
+ mSplitFromDesktopController = new SplitFromDesktopController(launcher);
+ }
+
private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId,
- Consumer<Boolean> callback) {
+ @Nullable Consumer<Boolean> callback, String transitionName) {
final RemoteSplitLaunchTransitionRunner animationRunner =
new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
return new RemoteTransition(animationRunner,
- ActivityThread.currentActivityThread().getApplicationThread(), "LaunchSplitPair");
+ ActivityThread.currentActivityThread().getApplicationThread(), transitionName);
}
private RemoteAnimationAdapter getLegacyRemoteAdapter(int firstTaskId, int secondTaskId,
- Consumer<Boolean> callback) {
+ @Nullable Consumer<Boolean> callback) {
final RemoteSplitLaunchAnimationRunner animationRunner =
new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback);
return new RemoteAnimationAdapter(animationRunner, 300, 150,
ActivityThread.currentActivityThread().getApplicationThread());
}
- private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1,
- int taskId, @StagePosition int stagePosition, float splitRatio,
- RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) {
- final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user);
- if (shortcutInfo != null) {
- mSystemUiProxy.startShortcutAndTask(shortcutInfo,
- options1.toBundle(), taskId, null /* options2 */, stagePosition,
- splitRatio, remoteTransition, shellInstanceId);
- } else {
- mSystemUiProxy.startIntentAndTask(getPendingIntent(intent, user), user.getIdentifier(),
- options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio,
- remoteTransition, shellInstanceId);
- }
- }
-
- private void launchIntentOrShortcutLegacy(Intent intent, UserHandle user,
- ActivityOptions options1, int taskId, @StagePosition int stagePosition,
- float splitRatio, RemoteAnimationAdapter adapter,
- @Nullable InstanceId shellInstanceId) {
- final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user);
- if (shortcutInfo != null) {
- mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
- options1.toBundle(), taskId, null /* options2 */, stagePosition,
- splitRatio, adapter, shellInstanceId);
- } else {
- mSystemUiProxy.startIntentAndTaskWithLegacyTransition(
- getPendingIntent(intent, user), user.getIdentifier(), options1.toBundle(),
- taskId, null /* options2 */, stagePosition, splitRatio, adapter,
- shellInstanceId);
- }
- }
-
- /**
- * We treat launching by intents as grouped in two ways,
- * If {@param intent} represents the first app, we always convert the intent to pending intent
- * It it represents second app, either the second intent OR mSecondPendingIntent will be used
- * convert second intent to a pendingIntent OR return mSecondPendingIntent as is
- */
- private PendingIntent getPendingIntent(Intent intent, UserHandle user) {
- boolean isParamFirstIntent = intent != null && intent == mInitialTaskIntent;
- if (!isParamFirstIntent && mSecondPendingIntent != null) {
- // Because mSecondPendingIntent and mSecondTaskIntent can't both be set, we know we need
- // to be using mSecondPendingIntent
- return mSecondPendingIntent;
- }
-
- // intent param must either be mInitialTaskIntent or mSecondTaskIntent, convert either to
- // a new PendingIntent
- return intent == null ? null : (user != null
- ? PendingIntent.getActivityAsUser(mContext, 0, intent,
- FLAG_MUTABLE | FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT, null /* options */, user)
- : PendingIntent.getActivity(mContext, 0, intent,
- FLAG_MUTABLE | FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT));
- }
-
public @StagePosition int getActiveSplitStagePosition() {
- return mInitialStagePosition;
+ return mSplitSelectDataHolder.getInitialStagePosition();
}
public StatsLogManager.EventEnum getSplitEvent() {
- return mSplitEvent;
+ return mSplitSelectDataHolder.getSplitEvent();
}
public void setRecentsAnimationRunning(boolean running) {
mRecentsAnimationRunning = running;
}
- @Nullable
- private ShortcutInfo getShortcutInfo(Intent intent, UserHandle user) {
- if (intent == null || intent.getPackage() == null) {
- return null;
- }
-
- final String shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID);
- if (shortcutId == null) {
- return null;
- }
-
- try {
- final Context context = mContext.createPackageContextAsUser(
- intent.getPackage(), 0 /* flags */, user);
- return new ShortcutInfo.Builder(context, shortcutId).build();
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage());
- }
-
- return null;
- }
-
public boolean isAnimateCurrentTaskDismissal() {
return mAnimateCurrentTaskDismissal;
}
@@ -706,7 +567,7 @@
private final Consumer<Boolean> mSuccessCallback;
RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId,
- Consumer<Boolean> callback) {
+ @Nullable Consumer<Boolean> callback) {
mInitialTaskId = initialTaskId;
mSecondTaskId = secondTaskId;
mSuccessCallback = callback;
@@ -716,6 +577,7 @@
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishedCallback) {
+ testLogD(LAUNCH_SPLIT_PAIR, "Received split startAnimation");
final Runnable finishAdapter = () -> {
try {
finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
@@ -731,9 +593,8 @@
if (mSuccessCallback != null) {
mSuccessCallback.accept(true);
}
+ resetState();
});
- // After successful launch, call resetState
- resetState();
});
}
@@ -754,7 +615,7 @@
private final Consumer<Boolean> mSuccessCallback;
RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId,
- Consumer<Boolean> successCallback) {
+ @Nullable Consumer<Boolean> successCallback) {
mInitialTaskId = initialTaskId;
mSecondTaskId = secondTaskId;
mSuccessCallback = successCallback;
@@ -790,26 +651,20 @@
}
/**
- * To be called if split select was cancelled
+ * To be called whenever we exit split selection state. If
+ * {@link FeatureFlags#ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE} is set, this should be the
+ * central way split is getting reset, which should then go through the callbacks to reset
+ * other state.
*/
public void resetState() {
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- mSplitSelectDataHolder.resetState();
- }
- mInitialTaskId = INVALID_TASK_ID;
- mInitialTaskIntent = null;
- mSecondTaskId = INVALID_TASK_ID;
- mSecondTaskIntent = null;
- mInitialUser = null;
- mSecondUser = null;
- mInitialStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
+ mSplitSelectDataHolder.resetState();
+ dispatchOnSplitSelectionExit();
mRecentsAnimationRunning = false;
mLaunchingTaskView = null;
- mItemInfo = null;
- mSplitEvent = null;
mAnimateCurrentTaskDismissal = false;
mDismissingFromSplitPair = false;
- mSecondPendingIntent = null;
+ mFirstFloatingTaskView = null;
+ mSplitInstructionsView = null;
}
/**
@@ -817,11 +672,7 @@
* chosen
*/
public boolean isSplitSelectActive() {
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- return mSplitSelectDataHolder.isSplitSelectActive();
- } else {
- return isInitialTaskIntentSet() && !isSecondTaskIntentSet();
- }
+ return mSplitSelectDataHolder.isSplitSelectActive();
}
/**
@@ -829,46 +680,35 @@
* be launched
*/
public boolean isBothSplitAppsConfirmed() {
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
- } else {
- return isInitialTaskIntentSet() && isSecondTaskIntentSet();
- }
- }
-
- private boolean isInitialTaskIntentSet() {
- return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null);
+ return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
}
public int getInitialTaskId() {
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- return mSplitSelectDataHolder.getInitialTaskId();
- } else {
- return mInitialTaskId;
- }
+ return mSplitSelectDataHolder.getInitialTaskId();
}
public int getSecondTaskId() {
- if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
- return mSplitSelectDataHolder.getSecondTaskId();
- } else {
- return mSecondTaskId;
- }
- }
-
- private boolean isSecondTaskIntentSet() {
- return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null
- || mSecondPendingIntent != null);
+ return mSplitSelectDataHolder.getSecondTaskId();
}
public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
mFirstFloatingTaskView = floatingTaskView;
}
+ public void setSplitInstructionsView(SplitInstructionsView splitInstructionsView) {
+ mSplitInstructionsView = splitInstructionsView;
+ }
+
+ @Nullable
public FloatingTaskView getFirstFloatingTaskView() {
return mFirstFloatingTaskView;
}
+ @Nullable
+ public SplitInstructionsView getSplitInstructionsView() {
+ return mSplitInstructionsView;
+ }
+
public AppPairsController getAppPairsController() {
return mAppPairsController;
}
@@ -878,4 +718,128 @@
mSplitSelectDataHolder.dump(prefix, writer);
}
}
+
+ public class SplitFromDesktopController {
+ private static final String TAG = "SplitFromDesktopController";
+
+ private final Launcher mLauncher;
+ private final OverviewComponentObserver mOverviewComponentObserver;
+ private final int mSplitPlaceholderSize;
+ private final int mSplitPlaceholderInset;
+ private ActivityManager.RunningTaskInfo mTaskInfo;
+ private ISplitSelectListener mSplitSelectListener;
+ private Drawable mAppIcon;
+
+ public SplitFromDesktopController(Launcher launcher) {
+ mLauncher = launcher;
+ RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(
+ launcher.getApplicationContext());
+ mOverviewComponentObserver =
+ new OverviewComponentObserver(launcher.getApplicationContext(), deviceState);
+ mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.split_placeholder_size);
+ mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.split_placeholder_inset);
+ mSplitSelectListener = new ISplitSelectListener.Stub() {
+ @Override
+ public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ int splitPosition, Rect taskBounds) {
+ if (!ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get()) return false;
+ MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
+ taskBounds));
+ return true;
+ }
+ };
+ SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener);
+ }
+
+ /**
+ * Enter split select from desktop mode.
+ * @param taskInfo the desktop task to move to split stage
+ * @param splitPosition the stage position used for this transition
+ * @param taskBounds the bounds of the task, used for {@link FloatingTaskView} animation
+ */
+ public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ int splitPosition, Rect taskBounds) {
+ mTaskInfo = taskInfo;
+ String packageName = mTaskInfo.realActivity.getPackageName();
+ PackageManager pm = mLauncher.getApplicationContext().getPackageManager();
+ IconProvider provider = new IconProvider(mLauncher.getApplicationContext());
+ try {
+ mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
+ PackageManager.ComponentInfoFlags.of(0)));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package not found: " + packageName, e);
+ }
+ RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
+ SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
+ false /* allowMinimizeSplitScreen */);
+
+ DesktopSplitRecentsAnimationListener listener =
+ new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds);
+
+ MAIN_EXECUTOR.execute(() -> {
+ callbacks.addListener(listener);
+ UI_HELPER_EXECUTOR.execute(
+ // Transition from app to enter stage split in launcher with
+ // recents animation.
+ () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+ mOverviewComponentObserver.getOverviewIntent(),
+ SystemClock.uptimeMillis(), callbacks, null, null));
+ });
+ }
+
+ private class DesktopSplitRecentsAnimationListener implements
+ RecentsAnimationCallbacks.RecentsAnimationListener {
+ private final Rect mTempRect = new Rect();
+ private final RectF mTaskBounds = new RectF();
+ private final int mSplitPosition;
+
+ DesktopSplitRecentsAnimationListener(int splitPosition, Rect taskBounds) {
+ mSplitPosition = splitPosition;
+ mTaskBounds.set(taskBounds);
+ }
+
+ @Override
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ StatsLogManager.LauncherEvent launcherDesktopSplitEvent =
+ mSplitPosition == STAGE_POSITION_BOTTOM_OR_RIGHT ?
+ LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM :
+ LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
+ setInitialTaskSelect(mTaskInfo, mSplitPosition,
+ null, launcherDesktopSplitEvent);
+
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
+ mSplitPlaceholderSize, mSplitPlaceholderInset,
+ mLauncher.getDeviceProfile(), getActiveSplitStagePosition(), mTempRect);
+
+ PendingAnimation anim = new PendingAnimation(
+ SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration());
+ final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(
+ mLauncher, mLauncher.getDragLayer(),
+ null /* thumbnail */,
+ mAppIcon, new RectF());
+ floatingTaskView.setAlpha(1);
+ floatingTaskView.addStagingAnimation(anim, mTaskBounds, mTempRect,
+ false /* fadeWithThumbnail */, true /* isStagedTask */);
+ setFirstFloatingTaskView(floatingTaskView);
+
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ controller.finish(true /* toRecents */, null /* onFinishComplete */,
+ false /* sendUserLeaveHint */);
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())
+ .onDesktopSplitSelectAnimComplete(mTaskInfo);
+ }
+ });
+ anim.buildAnim().start();
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
index f5b00cf..d1ec2b6 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -16,7 +16,7 @@
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import android.view.animation.Interpolator;
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 148a45a..056f9aa 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -16,6 +16,7 @@
package com.android.quickstep.util;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
@@ -169,6 +170,7 @@
private void cleanUp() {
mLauncher.getDragLayer().removeView(firstFloatingTaskView);
mLauncher.getDragLayer().removeView(secondFloatingTaskView);
+ mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher);
mController.resetState();
}
});
@@ -177,7 +179,8 @@
private boolean shouldIgnoreSecondSplitLaunch() {
return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
- && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get())
+ && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+ && !ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get())
|| !mController.isSplitSelectActive();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 24d8326..f3fa86a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -27,21 +27,15 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.ActivityManager;
-import android.content.Intent;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;
-import android.os.UserHandle;
-import android.view.View;
import androidx.annotation.BinderThread;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
@@ -143,11 +137,7 @@
.updateIconInBackground(
Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo,
false /* isLocked */),
- (task) -> {
- if (task.thumbnail != null) {
- floatingTaskView.setIcon(task.thumbnail.thumbnail);
- }
- });
+ (task) -> floatingTaskView.setIcon(task.icon));
floatingTaskView.setAlpha(1);
floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index cd5edab..6b3199f 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -15,11 +15,11 @@
*/
package com.android.quickstep.util;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
@@ -177,8 +177,8 @@
addDepthAnimationForState(launcher, NORMAL, duration);
- mAnimators.play(launcher.getRootView().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
- .setDuration(duration));
+ mAnimators.play(launcher.getRootView().getSysUiScrim().getSysUIMultiplier()
+ .animateToValue(0f, 1f).setDuration(duration));
}
private void addAnimationForPage(CellLayout page, int totalRows, long duration) {
diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java b/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java
index 441f88d..5fd86c0 100644
--- a/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java
+++ b/quickstep/src/com/android/quickstep/util/SurfaceTransaction.java
@@ -166,5 +166,10 @@
this.shadowRadius = radius;
return this;
}
+
+ @Override
+ public SurfaceProperties setShow() {
+ return this;
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 1112f4d..7cc2c46 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -26,7 +26,6 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.os.SystemProperties;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -153,14 +152,9 @@
// Create a new overlay layer. We do not call detach on this instance, it's propagated
// to other classes like PipTaskOrganizer / RecentsAnimationController to complete
// the cleanup.
- if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
- mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
- mAppBounds, new IconProvider(context).getIcon(mActivityInfo),
- appIconSizePx);
- } else {
- mPipContentOverlay = new PipContentOverlay.PipColorOverlay(view.getContext());
- }
+ mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
+ mAppBounds, new IconProvider(context).getIcon(mActivityInfo),
+ appIconSizePx);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
mPipContentOverlay.attach(tx, mLeash);
} else {
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index 9808b28..c82cdb7 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -24,11 +24,11 @@
import android.view.WindowMetrics;
import com.android.internal.policy.SystemBarUtils;
-import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
+import java.util.List;
import java.util.Set;
/**
@@ -54,16 +54,15 @@
}
@Override
- public ArrayMap<CachedDisplayInfo, WindowBounds[]> estimateInternalDisplayBounds(
+ public ArrayMap<CachedDisplayInfo, List<WindowBounds>> estimateInternalDisplayBounds(
Context displayInfoContext) {
- ArrayMap<CachedDisplayInfo, WindowBounds[]> result = new ArrayMap<>();
+ ArrayMap<CachedDisplayInfo, List<WindowBounds>> result = new ArrayMap<>();
WindowManager windowManager = displayInfoContext.getSystemService(WindowManager.class);
Set<WindowMetrics> possibleMaximumWindowMetrics =
windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
- FileLog.d("b/283944974", "possibleMaximumWindowMetrics: " + possibleMaximumWindowMetrics);
for (WindowMetrics windowMetrics : possibleMaximumWindowMetrics) {
CachedDisplayInfo info = getDisplayInfo(windowMetrics, Surface.ROTATION_0);
- WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info);
+ List<WindowBounds> bounds = estimateWindowBounds(displayInfoContext, info);
result.put(info, bounds);
}
return result;
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
index 7b122c6..ddc796f 100644
--- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -23,6 +23,10 @@
import android.os.IBinder;
import android.util.Log;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.TouchInteractionService.TISBinder;
@@ -50,6 +54,7 @@
private short mConnectionAttempts;
private boolean mTisServiceBound;
private boolean mIsConnected;
+ @Nullable private TISBinder mBinder;
public TISBindHelper(Context context, Consumer<TISBinder> connectionCallback) {
mContext = context;
@@ -70,7 +75,8 @@
Log.d(TAG, "TIS service connected");
mIsConnected = true;
- mConnectionCallback.accept((TISBinder) iBinder);
+ mBinder = (TISBinder) iBinder;
+ mConnectionCallback.accept(mBinder);
// Flush the pending callbacks
for (Runnable r : mPendingConnectedCallbacks) {
r.run();
@@ -80,7 +86,11 @@
}
@Override
- public void onServiceDisconnected(ComponentName componentName) { }
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.d(TAG, "TIS service disconnected");
+ mBinder = null;
+ mIsConnected = false;
+ }
@Override
public void onBindingDied(ComponentName name) {
@@ -88,6 +98,21 @@
internalBindToTIS();
}
+ @Nullable
+ public TISBinder getBinder() {
+ return mBinder;
+ }
+
+ @Nullable
+ public TaskbarManager getTaskbarManager() {
+ return mBinder == null ? null : mBinder.getTaskbarManager();
+ }
+
+ @Nullable
+ public OverviewCommandHelper getOverviewCommandHelper() {
+ return mBinder == null ? null : mBinder.getOverviewCommandHelper();
+ }
+
/**
* Runs the given {@param r} runnable when the service is connected.
*/
@@ -139,6 +164,7 @@
public void onDestroy() {
internalUnbindToTIS();
resetServiceBindRetryState();
+ mBinder = null;
mIsConnected = false;
mPendingConnectedCallbacks.clear();
}
diff --git a/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
index bf8612a..8804049 100644
--- a/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
@@ -16,7 +16,7 @@
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
import android.view.animation.Interpolator;
diff --git a/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java
index cbf46bf..5463d84 100644
--- a/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java
@@ -16,7 +16,7 @@
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
import android.view.animation.Interpolator;
@@ -36,8 +36,8 @@
public int getGridSlideDuration() { return 500; }
public int getDuration() { return TABLET_ENTER_DURATION; }
- public Interpolator getStagedRectXInterpolator() { return DEACCEL_2; }
- public Interpolator getStagedRectYInterpolator() { return DEACCEL_2; }
- public Interpolator getStagedRectScaleXInterpolator() { return DEACCEL_2; }
- public Interpolator getStagedRectScaleYInterpolator() { return DEACCEL_2; }
+ public Interpolator getStagedRectXInterpolator() { return DECELERATE_2; }
+ public Interpolator getStagedRectYInterpolator() { return DECELERATE_2; }
+ public Interpolator getStagedRectScaleXInterpolator() { return DECELERATE_2; }
+ public Interpolator getStagedRectScaleYInterpolator() { return DECELERATE_2; }
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 303a528..48dadd1 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -112,8 +112,7 @@
mContext = context;
mSizeStrategy = sizeStrategy;
- // TODO(b/187074722): Don't create this per-TaskViewSimulator
- mOrientationState = TraceHelper.allowIpcs("",
+ mOrientationState = TraceHelper.allowIpcs("TaskViewSimulator.init",
() -> new RecentsOrientedState(context, sizeStrategy, i -> { }));
mOrientationState.setGestureActive(true);
mCurrentFullscreenParams = new FullscreenDrawParams(context);
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index 0f20e43..1cbded6 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -21,8 +21,8 @@
import android.util.FloatProperty;
import android.view.RemoteAnimationTarget;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
@@ -153,7 +153,8 @@
// Fade out Assistant overlay.
if (activityType == ACTIVITY_TYPE_ASSISTANT && app.isNotInRecents) {
float progress = Utilities.boundToRange(getProgress(), 0, 1);
- builder.setAlpha(1 - Interpolators.DEACCEL_2_5.getInterpolation(progress));
+ builder.setAlpha(1 - Interpolators.DECELERATE_QUINT
+ .getInterpolation(progress));
} else {
builder.setAlpha(getTargetAlpha());
}
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
index 34fa7f1..0a97793 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
@@ -32,11 +32,11 @@
import android.util.FloatProperty;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.states.StateAnimationConfig;
@@ -92,7 +92,8 @@
}
// Add sysui scrim animation.
- mAnimators.play(launcher.getRootView().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f));
+ mAnimators.play(launcher.getRootView().getSysUiScrim()
+ .getSysUIMultiplier().animateToValue(0f, 1f));
mAnimators.setDuration(DURATION_MS);
mAnimators.setInterpolator(Interpolators.DECELERATED_EASE);
diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
index 716d389..fdc8f1f 100644
--- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -15,12 +15,12 @@
*/
package com.android.quickstep.views;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN;
import android.animation.Animator;
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 6813857..19ac1f8 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -16,6 +16,8 @@
package com.android.quickstep.views;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
+
import android.content.Context;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -248,8 +250,15 @@
*/
private float getOriginalTranslationY() {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- return deviceProfile.isTablet
- ? deviceProfile.overviewRowSpacing
- : deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f;
+ if (deviceProfile.isTablet) {
+ if (ENABLE_GRID_ONLY_OVERVIEW.get()) {
+ return (getRecentsView().getLastComputedTaskSize().height()
+ + deviceProfile.overviewTaskThumbnailTopMarginPx) / 2.0f
+ + deviceProfile.overviewRowSpacing;
+ } else {
+ return deviceProfile.overviewRowSpacing;
+ }
+ }
+ return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f;
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopAppSelectView.java b/quickstep/src/com/android/quickstep/views/DesktopAppSelectView.java
new file mode 100644
index 0000000..a5be142
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/DesktopAppSelectView.java
@@ -0,0 +1,179 @@
+/*
+ * 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.quickstep.views;
+
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.app.animation.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+/**
+ * Floating view show on launcher home screen that notifies the user that an app will be launched to
+ * the desktop.
+ */
+public class DesktopAppSelectView extends LinearLayout {
+
+ private static final int SHOW_INITIAL_HEIGHT_DP = 7;
+ private static final int SHOW_CONTAINER_SCALE_DURATION = 333;
+ private static final int SHOW_CONTAINER_ALPHA_DURATION = 83;
+ private static final int SHOW_CONTENT_ALPHA_DELAY = 67;
+ private static final int SHOW_CONTENT_ALPHA_DURATION = 83;
+ private static final int HIDE_DURATION = 83;
+
+ private final Launcher mLauncher;
+
+ private View mText;
+ private View mCloseButton;
+ @Nullable
+ private Runnable mOnCloseCallback;
+ private AnimatorSet mShowAnimation;
+ private Animator mHideAnimation;
+
+ public DesktopAppSelectView(Context context) {
+ this(context, null);
+ }
+
+ public DesktopAppSelectView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public DesktopAppSelectView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mLauncher = Launcher.getLauncher(context);
+ }
+
+ /**
+ * Show the popup on launcher home screen
+ *
+ * @param onCloseCallback optional callback that is called when user clicks the close button
+ * @return the created view
+ */
+ public static DesktopAppSelectView show(Launcher launcher, @Nullable Runnable onCloseCallback) {
+ DesktopAppSelectView view = (DesktopAppSelectView) launcher.getLayoutInflater().inflate(
+ R.layout.floating_desktop_app_select, launcher.getDragLayer(), false);
+ view.setOnCloseClickCallback(onCloseCallback);
+ view.show();
+ return view;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mText = findViewById(R.id.desktop_app_select_text);
+ mCloseButton = findViewById(R.id.close_button);
+ mCloseButton.setOnClickListener(v -> {
+ if (mHideAnimation == null) {
+ hide();
+ if (mOnCloseCallback != null) {
+ mOnCloseCallback.run();
+ }
+ }
+ });
+ }
+
+ private void show() {
+ mLauncher.getDragLayer().addView(this);
+
+ // Set up initial values
+ getBackground().setAlpha(0);
+ mText.setAlpha(0);
+ mCloseButton.setAlpha(0);
+ int initialHeightPx = Utilities.dpToPx(SHOW_INITIAL_HEIGHT_DP);
+ int finalHeight = getResources().getDimensionPixelSize(
+ R.dimen.desktop_mode_floating_app_select_height);
+ float initialScale = initialHeightPx / (float) finalHeight;
+ setScaleY(initialScale);
+ setPivotY(0);
+
+ // Animate the container
+ ValueAnimator containerBackground = ValueAnimator.ofInt(0, 255);
+ containerBackground.addUpdateListener(
+ animation -> getBackground().setAlpha((Integer) animation.getAnimatedValue()));
+ containerBackground.setDuration(SHOW_CONTAINER_ALPHA_DURATION);
+ containerBackground.setInterpolator(LINEAR);
+
+ ObjectAnimator containerSize = ObjectAnimator.ofFloat(this, SCALE_Y, 1f);
+ containerSize.setDuration(SHOW_CONTAINER_SCALE_DURATION);
+ containerSize.setInterpolator(EMPHASIZED_DECELERATE);
+
+ // Animate the contents
+ ObjectAnimator textAlpha = ObjectAnimator.ofFloat(mText, ALPHA, 1);
+ ObjectAnimator buttonAlpha = ObjectAnimator.ofFloat(mCloseButton, ALPHA, 1);
+ AnimatorSet contentAlpha = new AnimatorSet();
+ contentAlpha.playTogether(textAlpha, buttonAlpha);
+ contentAlpha.setStartDelay(SHOW_CONTENT_ALPHA_DELAY);
+ contentAlpha.setDuration(SHOW_CONTENT_ALPHA_DURATION);
+ contentAlpha.setInterpolator(LINEAR);
+
+ // Start the animation
+ mShowAnimation = new AnimatorSet();
+ mShowAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mShowAnimation = null;
+ }
+ });
+ mShowAnimation.playTogether(containerBackground, containerSize, contentAlpha);
+ mShowAnimation.start();
+ }
+
+ /**
+ * Hide the floating view
+ */
+ public void hide() {
+ if (mShowAnimation != null) {
+ mShowAnimation.cancel();
+ }
+ mHideAnimation = ObjectAnimator.ofFloat(this, ALPHA, 0);
+ mHideAnimation.setDuration(HIDE_DURATION).setInterpolator(LINEAR);
+ mHideAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mLauncher.getDragLayer().removeView(DesktopAppSelectView.this);
+ mHideAnimation = null;
+ }
+ });
+ mHideAnimation.start();
+ }
+
+ /**
+ * Add a callback that is called when close button is clicked
+ */
+ public void setOnCloseClickCallback(@Nullable Runnable callback) {
+ mOnCloseCallback = callback;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 1cfaf14..dfd4390 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -43,6 +44,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
@@ -51,6 +53,7 @@
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.QuickStepContract;
import java.util.ArrayList;
import java.util.Arrays;
@@ -79,7 +82,7 @@
private static final String TAG = DesktopTaskView.class.getSimpleName();
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
@NonNull
private List<Task> mTasks = new ArrayList<>();
@@ -91,6 +94,8 @@
private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
+ private final TaskView.FullscreenDrawParams mSnapshotDrawParams;
+
private View mBackgroundView;
public DesktopTaskView(Context context) {
@@ -103,6 +108,18 @@
public DesktopTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+
+ mSnapshotDrawParams = new FullscreenDrawParams(context) {
+ @Override
+ public float computeTaskCornerRadius(Context context) {
+ return QuickStepContract.getWindowCornerRadius(context);
+ }
+
+ @Override
+ public float computeWindowCornerRadius(Context context) {
+ return QuickStepContract.getWindowCornerRadius(context);
+ }
+ };
}
@Override
@@ -213,7 +230,22 @@
private TaskIdAttributeContainer createAttributeContainer(Task task,
TaskThumbnailView thumbnailView) {
- return new TaskIdAttributeContainer(task, thumbnailView, null, STAGE_POSITION_UNDEFINED);
+ return new TaskIdAttributeContainer(task, thumbnailView, createIconView(task),
+ STAGE_POSITION_UNDEFINED);
+ }
+
+ private IconView createIconView(Task task) {
+ IconView iconView = new IconView(mContext);
+ PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ try {
+ IconProvider provider = new IconProvider(mContext);
+ Drawable appIcon = provider.getIcon(pm.getActivityInfo(task.topActivity,
+ PackageManager.ComponentInfoFlags.of(0)));
+ iconView.setDrawable(appIcon);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package not found: " + task.topActivity.getPackageName(), e);
+ }
+ return iconView;
}
@Nullable
@@ -465,14 +497,20 @@
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
- updateSnapshotRadius();
}
+ updateSnapshotRadius();
}
@Override
protected void updateSnapshotRadius() {
+ super.updateSnapshotRadius();
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- mSnapshotViewMap.valueAt(i).setFullscreenParams(mCurrentFullscreenParams);
+ if (i == 0) {
+ // All snapshots share the same params. Only update it with the first snapshot.
+ updateFullscreenParams(mSnapshotDrawParams,
+ mSnapshotView.getPreviewPositionHelper());
+ }
+ mSnapshotViewMap.valueAt(i).setFullscreenParams(mSnapshotDrawParams);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 7bbe36a..e5a0e10 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -145,23 +145,29 @@
mAppUsageLimitTimeMs = mAppRemainingTimeMs = -1;
mTask = task;
THREAD_POOL_EXECUTOR.execute(() -> {
- final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
- mTask.getTopComponent().getPackageName(),
- UserHandle.of(mTask.key.userId));
+ AppUsageLimit usageLimit = null;
+ try {
+ usageLimit = mLauncherApps.getAppUsageLimit(
+ mTask.getTopComponent().getPackageName(),
+ UserHandle.of(mTask.key.userId));
+ } catch (Exception e) {
+ Log.e(TAG, "Error initializing digital well being toast", e);
+ }
+ final long appUsageLimitTimeMs =
+ usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
+ final long appRemainingTimeMs =
+ usageLimit != null ? usageLimit.getUsageRemaining() : -1;
- final long appUsageLimitTimeMs =
- usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
- final long appRemainingTimeMs =
- usageLimit != null ? usageLimit.getUsageRemaining() : -1;
+ mTaskView.post(() -> {
+ if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
+ setNoLimit();
+ } else {
+ setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
+ }
+ });
- mTaskView.post(() -> {
- if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
- setNoLimit();
- } else {
- setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
}
- });
- });
+ );
}
public void setSplitConfiguration(SplitBounds splitBounds) {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 75a8ea2..f250b8c 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,8 +1,8 @@
package com.android.quickstep.views;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -11,7 +11,6 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -213,8 +212,8 @@
mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
}
- public void setIcon(Bitmap icon) {
- mSplitPlaceholderView.setIcon(new BitmapDrawable(icon), mSplitHolderSize);
+ public void setIcon(Drawable drawable) {
+ mSplitPlaceholderView.setIcon(drawable, mSplitHolderSize);
}
protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index c91b183..01f6ae8 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -223,11 +223,12 @@
// Callbacks run from remote animation when recents animation not currently running
InteractionJankMonitorWrapper.begin(this,
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
- launchTask(success -> {
+ launchTaskInternal(success -> {
endCallback.executeAllAndDestroy();
InteractionJankMonitorWrapper.end(
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
- }, false /* freezeTaskList */);
+ }, false /* freezeTaskList */, true /*launchingExistingTaskview*/);
+
// Callbacks get run from recentsView for case when recents animation already running
recentsView.addSideTaskLaunchCallback(endCallback);
@@ -236,7 +237,19 @@
@Override
public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
- getRecentsView().getSplitSelectController().launchExistingSplitPair(this, mTask.key.id,
+ launchTaskInternal(callback, isQuickswitch, false /*launchingExistingTaskview*/);
+ }
+
+ /**
+ * @param launchingExistingTaskView {@link SplitSelectStateController#launchExistingSplitPair}
+ * uses existence of GroupedTaskView as control flow of how to animate in the incoming task. If
+ * we're launching from overview (from overview thumbnails) then pass in {@code true},
+ * otherwise pass in {@code false} for case like quickswitching from home to task
+ */
+ private void launchTaskInternal(@NonNull Consumer<Boolean> callback, boolean isQuickswitch,
+ boolean launchingExistingTaskView) {
+ getRecentsView().getSplitSelectController().launchExistingSplitPair(
+ launchingExistingTaskView ? this : null, mTask.key.id,
mSecondaryTask.key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
callback, isQuickswitch, getSplitRatio());
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 4dbf4e3..ebe4c66 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -30,18 +30,20 @@
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
-import android.view.Surface;
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.PendingSplitSelectInfo;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -81,14 +83,23 @@
}
@Override
- public void startHome(boolean animated) {
+ protected void handleStartHome(boolean animated) {
StateManager stateManager = mActivity.getStateManager();
animated &= stateManager.shouldAnimateStateChange();
stateManager.goToState(NORMAL, animated);
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.getSplitAnimationController()
+ .playPlaceholderDismissAnim(mActivity);
+ }
AbstractFloatingView.closeAllOpenViews(mActivity, animated);
}
@Override
+ protected boolean canStartHomeSafely() {
+ return mActivity.canStartHomeSafely();
+ }
+
+ @Override
protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
mActivity.getStateManager().moveToRestState();
@@ -118,7 +129,9 @@
@Override
public void reset() {
super.reset();
- setLayoutRotation(Surface.ROTATION_0, Surface.ROTATION_0);
+
+ int recentsActivityRotation = getPagedViewOrientedState().getRecentsActivityRotation();
+ setLayoutRotation(recentsActivityRotation, recentsActivityRotation);
}
@Override
@@ -155,6 +168,8 @@
@Override
public void setOverviewStateEnabled(boolean enabled) {
super.setOverviewStateEnabled(enabled);
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "overview state enabled state has changed: "
+ + enabled);
if (enabled) {
LauncherState state = mActivity.getStateManager().getState();
boolean hasClearAllButton = (state.getVisibleElements(mActivity)
@@ -218,7 +233,11 @@
@Override
protected boolean canLaunchFullscreenTask() {
- return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ return !mSplitSelectStateController.isSplitSelectActive();
+ } else {
+ return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+ }
}
@Override
@@ -228,7 +247,7 @@
DesktopVisibilityController desktopVisibilityController =
mActivity.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
- desktopVisibilityController.setGestureInProgress(true);
+ desktopVisibilityController.setRecentsGestureStart();
}
}
@@ -236,9 +255,11 @@
public void onGestureAnimationEnd() {
DesktopVisibilityController desktopVisibilityController = null;
boolean showDesktopApps = false;
+ GestureState.GestureEndTarget endTarget = null;
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
desktopVisibilityController = mActivity.getDesktopVisibilityController();
- if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.LAST_TASK
+ endTarget = mCurrentGestureEndTarget;
+ if (endTarget == 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
@@ -247,7 +268,7 @@
}
super.onGestureAnimationEnd();
if (desktopVisibilityController != null) {
- desktopVisibilityController.setGestureInProgress(false);
+ desktopVisibilityController.setRecentsGestureEnd(endTarget);
}
if (showDesktopApps) {
SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId());
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index e47c089..b31791a 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -258,6 +258,13 @@
}
/**
+ * Returns the visibility of the overview actions buttons.
+ */
+ public @Visibility int getActionsButtonVisibility() {
+ return findViewById(R.id.action_buttons).getVisibility();
+ }
+
+ /**
* Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
*/
private void updatePadding() {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 55b9f95..76f9c2c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -21,6 +21,16 @@
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static com.android.app.animation.Interpolators.ACCELERATE;
+import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.OVERSHOOT_0_75;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
@@ -32,18 +42,7 @@
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCH_FROM_STAGED_APP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
@@ -54,6 +53,7 @@
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.util.LogUtils.splitFailureMessage;
import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
@@ -165,6 +165,7 @@
import com.android.launcher3.util.ViewPool;
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.GestureState;
+import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.RecentsAnimationController;
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsFilterState;
@@ -173,6 +174,7 @@
import com.android.quickstep.RemoteTargetGluer;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.SplitSelectionListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
@@ -211,6 +213,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -661,7 +664,8 @@
/**
* Placeholder view indicating where the first split screen selected app will be placed
*/
- private SplitSelectStateController mSplitSelectStateController;
+ protected SplitSelectStateController mSplitSelectStateController;
+
/**
* The first task that split screen selection was initiated with. When split select state is
* initialized, we create a
@@ -679,11 +683,22 @@
private final Toast mSplitUnsupportedToast = Toast.makeText(getContext(),
R.string.toast_split_app_unsupported, Toast.LENGTH_SHORT);
- private SplitInstructionsView mSplitInstructionsView;
-
@Nullable
private SplitSelectSource mSplitSelectSource;
+ private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() {
+ @Override
+ public void onSplitSelectionConfirmed() { }
+
+ @Override
+ public void onSplitSelectionActive() { }
+
+ @Override
+ public void onSplitSelectionExit(boolean launchedSplit) {
+ resetFromSplitSelectionState();
+ }
+ };
+
/**
* Keeps track of the index of the TaskView that split screen was initialized with so we know
* where to insert it back into list of taskViews in case user backs out of entering split
@@ -694,8 +709,6 @@
*/
private int mSplitHiddenTaskViewIndex = -1;
@Nullable
- private FloatingTaskView mFirstFloatingTaskView;
- @Nullable
private FloatingTaskView mSecondFloatingTaskView;
/**
@@ -992,16 +1005,34 @@
}
/**
- * Update the thumbnail of the task.
+ * Update the thumbnail(s) of the relevant TaskView.
* @param refreshNow Refresh immediately if it's true.
*/
@Nullable
- public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
- TaskView taskView = getTaskViewByTaskId(taskId);
- if (taskView != null) {
- taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
+ public TaskView updateThumbnail(
+ HashMap<Integer, ThumbnailData> thumbnailData, boolean refreshNow) {
+ TaskView updatedTaskView = null;
+ for (Map.Entry<Integer, ThumbnailData> entry : thumbnailData.entrySet()) {
+ Integer id = entry.getKey();
+ ThumbnailData thumbnail = entry.getValue();
+ TaskView taskView = getTaskViewByTaskId(id);
+ if (taskView == null) {
+ continue;
+ }
+ // taskView could be a GroupedTaskView, so select the relevant task by ID
+ TaskIdAttributeContainer taskAttributes = taskView.getTaskAttributesById(id);
+ if (taskAttributes == null) {
+ continue;
+ }
+ Task task = taskAttributes.getTask();
+ TaskThumbnailView taskThumbnailView = taskAttributes.getThumbnailView();
+ taskThumbnailView.setThumbnail(task, thumbnail, refreshNow);
+ // thumbnailData can contain 1-2 ids, but they should correspond to the same
+ // TaskView, so overwriting is ok
+ updatedTaskView = taskView;
}
- return taskView;
+
+ return updatedTaskView;
}
@Override
@@ -1048,6 +1079,9 @@
mIPipAnimationListener);
mOrientationState.initListeners();
mTaskOverlayFactory.initListeners();
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
+ }
}
@Override
@@ -1066,6 +1100,9 @@
mIPipAnimationListener.setActivityAndRecentsView(null, null);
mOrientationState.destroyListeners();
mTaskOverlayFactory.removeListeners();
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
+ }
}
@Override
@@ -1180,7 +1217,7 @@
new SurfaceTransactionApplier(mActivity.getDragLayer());
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
- appAnimator.setInterpolator(ACCEL_DEACCEL);
+ appAnimator.setInterpolator(ACCELERATE_DECELERATE);
appAnimator.addUpdateListener(valueAnimator -> {
float percent = valueAnimator.getAnimatedFraction();
SurfaceTransaction transaction = new SurfaceTransaction();
@@ -1330,9 +1367,15 @@
return null;
}
+ // We're looking for a taskView that matches these ids, regardless of order
+ int[] taskIdsCopy = Arrays.copyOf(taskIds, taskIds.length);
+ Arrays.sort(taskIdsCopy);
+
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = requireTaskViewAt(i);
- if (Arrays.equals(taskIds, taskView.getTaskIds())) {
+ int[] taskViewIdsCopy = taskView.getTaskIds();
+ Arrays.sort(taskViewIdsCopy);
+ if (Arrays.equals(taskIdsCopy, taskViewIdsCopy)) {
return taskView;
}
}
@@ -1353,6 +1396,7 @@
// its thumbnail
mTmpRunningTasks = null;
mSplitBoundsConfig = null;
+ mTaskOverlayFactory.clearAllActiveState();
}
updateLocusId();
}
@@ -1631,6 +1675,10 @@
int[] runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId);
int[] focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId);
+ // Reset the focused task to avoiding initializing TaskViews layout as focused task during
+ // binding. The focused task view will be updated after all the TaskViews are bound.
+ mFocusedTaskViewId = INVALID_TASK_ID;
+
// Removing views sets the currentPage to 0, so we save this and restore it after
// the new set of views are added
int previousCurrentPage = mCurrentPage;
@@ -1639,8 +1687,8 @@
// If we are entering Overview as a result of initiating a split from somewhere else
// (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail.
int stagedTaskIdToBeRemovedFromGrid;
- if (mSplitSelectSource != null) {
- stagedTaskIdToBeRemovedFromGrid = mSplitSelectSource.alreadyRunningTaskId;
+ if (isSplitSelectionActive()) {
+ stagedTaskIdToBeRemovedFromGrid = mSplitSelectStateController.getInitialTaskId();
updateCurrentTaskActionsVisibility();
} else {
stagedTaskIdToBeRemovedFromGrid = INVALID_TASK_ID;
@@ -1737,7 +1785,9 @@
mFocusedTaskViewId = newFocusedTaskView != null && !ENABLE_GRID_ONLY_OVERVIEW.get()
? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID;
updateTaskSize();
- updateChildTaskOrientations();
+ if (newFocusedTaskView != null) {
+ newFocusedTaskView.setOrientationState(mOrientationState);
+ }
TaskView newRunningTaskView = null;
if (hasAnyValidTaskIds(runningTaskId)) {
@@ -2322,7 +2372,15 @@
startHome(mActivity.isStarted());
}
- public abstract void startHome(boolean animated);
+ public void startHome(boolean animated) {
+ if (!canStartHomeSafely()) return;
+ handleStartHome(animated);
+ }
+
+ protected abstract void handleStartHome(boolean animated);
+
+ /** Returns whether user can start home based on state in {@link OverviewCommandHelper}. */
+ protected abstract boolean canStartHomeSafely();
public void reset() {
setCurrentTask(-1);
@@ -2344,7 +2402,9 @@
remoteTargetHandle.getTransformParams().setTargetSet(null);
remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
});
- resetFromSplitSelectionState();
+ if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ resetFromSplitSelectionState();
+ }
// These are relatively expensive and don't need to be done this frame (RecentsView isn't
// visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -3126,7 +3186,7 @@
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// alpha is set to 0 so that it can be recycled in the view pool properly
anim.setFloat(taskView, VIEW_ALPHA, 0,
- clampToProgress(isOnGridBottomRow(taskView) ? ACCEL : FINAL_FRAME, 0, 0.5f));
+ clampToProgress(isOnGridBottomRow(taskView) ? ACCELERATE : FINAL_FRAME, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
taskView.getSecondaryDismissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -3165,7 +3225,7 @@
AnimUtils.getDeviceOverviewToSplitTimings(mActivity.getDeviceProfile().isTablet);
RectF startingTaskRect = new RectF();
- safeRemoveDragLayerView(mFirstFloatingTaskView);
+ safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
SplitAnimInitProps splitAnimInitProps =
mSplitSelectStateController.getSplitAnimationController().getFirstAnimInitViews(
() -> mSplitHiddenTaskView, () -> mSplitSelectSource);
@@ -3178,33 +3238,34 @@
timings.getIconFadeEndOffset()));
}
- mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+ FloatingTaskView firstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
splitAnimInitProps.getOriginalView(),
splitAnimInitProps.getOriginalBitmap(),
splitAnimInitProps.getIconDrawable(), startingTaskRect);
- mFirstFloatingTaskView.setAlpha(1);
- mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
+ firstFloatingTaskView.setAlpha(1);
+ firstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
+ mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView);
// Allow user to click staged app to launch into fullscreen
- if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
- mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
- }
+ firstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
// SplitInstructionsView: animate in
- safeRemoveDragLayerView(mSplitInstructionsView);
- mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
- mSplitInstructionsView.setAlpha(0);
- anim.setViewAlpha(mSplitInstructionsView, 1, clampToProgress(LINEAR,
+ safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
+ SplitInstructionsView splitInstructionsView =
+ SplitInstructionsView.getSplitInstructionsView(mActivity);
+ splitInstructionsView.setAlpha(0);
+ anim.setViewAlpha(splitInstructionsView, 1, clampToProgress(LINEAR,
timings.getInstructionsContainerFadeInStartOffset(),
timings.getInstructionsContainerFadeInEndOffset()));
- anim.setViewAlpha(mSplitInstructionsView.getTextView(), 1, clampToProgress(LINEAR,
+ anim.setViewAlpha(splitInstructionsView.getTextView(), 1, clampToProgress(LINEAR,
timings.getInstructionsTextFadeInStartOffset(),
timings.getInstructionsTextFadeInEndOffset()));
- anim.addFloat(mSplitInstructionsView, mSplitInstructionsView.UNFOLD, 0.1f, 1,
+ anim.addFloat(splitInstructionsView, splitInstructionsView.UNFOLD, 0.1f, 1,
clampToProgress(EMPHASIZED_DECELERATE,
timings.getInstructionsUnfoldStartOffset(),
timings.getInstructionsUnfoldEndOffset()));
+ mSplitSelectStateController.setSplitInstructionsView(splitInstructionsView);
InteractionJankMonitorWrapper.begin(this,
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
@@ -3227,7 +3288,11 @@
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
} else {
// If transition to split select was interrupted, clean up to prevent glitches
- resetFromSplitSelectionState();
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.resetState();
+ } else {
+ resetFromSplitSelectionState();
+ }
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
}
@@ -3260,8 +3325,13 @@
true /* isStagedTask */);
pendingAnimation.addEndListener(animationSuccess ->
- mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess ->
- resetFromSplitSelectionState()));
+ mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess -> {
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.resetState();
+ } else {
+ resetFromSplitSelectionState();
+ }
+ }));
pendingAnimation.buildAnim().start();
}
@@ -3968,13 +4038,12 @@
private void removeTaskInternal(int dismissedTaskViewId) {
int[] taskIds = getTaskIdsForTaskViewId(dismissedTaskViewId);
- int primaryTaskId = taskIds[0];
- int secondaryTaskId = taskIds[1];
UI_HELPER_EXECUTOR.getHandler().post(
() -> {
- ActivityManagerWrapper.getInstance().removeTask(primaryTaskId);
- if (secondaryTaskId != -1) {
- ActivityManagerWrapper.getInstance().removeTask(secondaryTaskId);
+ for (int taskId : taskIds) {
+ if (taskId != -1) {
+ ActivityManagerWrapper.getInstance().removeTask(taskId);
+ }
}
});
}
@@ -4294,7 +4363,7 @@
private void updatePageOffsets() {
float offset = mAdjacentPageHorizontalOffset;
- float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
+ float modalOffset = ACCELERATE_0_75.getInterpolation(mTaskModalness);
int count = getChildCount();
boolean showAsGrid = showAsGrid();
@@ -4662,6 +4731,8 @@
return false;
}
if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
+ Log.w(TAG, splitFailureMessage(
+ "confirmSplitSelect", "both apps have already been set"));
return true;
}
// Second task is selected either as an already-running Task or an Intent
@@ -4669,6 +4740,9 @@
if (!task.isDockable) {
// Task does not support split screen
mSplitUnsupportedToast.show();
+ Log.w(TAG, splitFailureMessage("confirmSplitSelect",
+ "selected Task (" + task.key.getPackageName()
+ + ") is not dockable / does not support splitscreen"));
return true;
}
mSplitSelectStateController.setSecondTask(task);
@@ -4693,8 +4767,10 @@
mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
secondTaskEndingBounds);
- mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
- mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
+ FloatingTaskView firstFloatingTaskView =
+ mSplitSelectStateController.getFirstFloatingTaskView();
+ firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
+ firstFloatingTaskView.addConfirmAnimation(pendingAnimation,
new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
false /* fadeWithThumbnail */, true /* isStagedTask */);
@@ -4706,14 +4782,21 @@
mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
- pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
- timings.getInstructionsFadeStartOffset(),
- timings.getInstructionsFadeEndOffset()));
+ pendingAnimation.setViewAlpha(mSplitSelectStateController.getSplitInstructionsView(), 0,
+ clampToProgress(LINEAR, timings.getInstructionsFadeStartOffset(),
+ timings.getInstructionsFadeEndOffset()));
pendingAnimation.addEndListener(aBoolean -> {
mSplitSelectStateController.launchSplitTasks(
- aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
- InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ aBoolean1 -> {
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mSplitSelectStateController.resetState();
+ } else {
+ resetFromSplitSelectionState();
+ }
+ InteractionJankMonitorWrapper.end(
+ InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ });
});
mSecondSplitHiddenView = containerTaskView;
@@ -4727,21 +4810,22 @@
// Fade out all other views underneath placeholders
ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
- pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
+ pendingAnimation.add(tvFade, DECELERATE_2, SpringProperty.DEFAULT);
pendingAnimation.buildAnim().start();
return true;
}
@SuppressLint("WrongCall")
protected void resetFromSplitSelectionState() {
- if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
- safeRemoveDragLayerView(mFirstFloatingTaskView);
+ if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1 ||
+ FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
safeRemoveDragLayerView(mSecondFloatingTaskView);
- safeRemoveDragLayerView(mSplitInstructionsView);
- mFirstFloatingTaskView = null;
+ safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
mSecondFloatingTaskView = null;
- mSplitInstructionsView = null;
mSplitSelectSource = null;
+ mSplitSelectStateController.getSplitAnimationController()
+ .removeSplitInstructionsView(mActivity);
}
if (mSecondSplitHiddenView != null) {
@@ -4754,7 +4838,11 @@
setTaskViewsPrimarySplitTranslation(0);
setTaskViewsSecondarySplitTranslation(0);
- mSplitSelectStateController.resetState();
+ if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ // When flag is on, this method gets called from resetState() call below, let's avoid
+ // infinite recursion today
+ mSplitSelectStateController.resetState();
+ }
if (mSplitHiddenTaskViewIndex == -1) {
return;
}
@@ -4821,8 +4909,10 @@
mSplitPlaceholderInset, mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
mTempRectF.set(mTempRect);
- mFirstFloatingTaskView.updateOrientationHandler(mOrientationHandler);
- mFirstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
+ FloatingTaskView firstFloatingTaskView =
+ mSplitSelectStateController.getFirstFloatingTaskView();
+ firstFloatingTaskView.updateOrientationHandler(mOrientationHandler);
+ firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
PagedOrientationHandler orientationHandler = getPagedOrientationHandler();
Pair<FloatProperty, FloatProperty> taskViewsFloat =
@@ -4832,8 +4922,8 @@
taskViewsFloat.first.set(this, getSplitSelectTranslation());
taskViewsFloat.second.set(this, 0f);
- if (mSplitInstructionsView != null) {
- mSplitInstructionsView.ensureProperRotation();
+ if (mSplitSelectStateController.getSplitInstructionsView() != null) {
+ mSplitSelectStateController.getSplitInstructionsView().ensureProperRotation();
}
}
@@ -5152,7 +5242,8 @@
true /* forDesktop */);
mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets);
} else {
- gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
+ gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
+ false);
mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
}
mSplitBoundsConfig = gluer.getSplitBounds();
@@ -5207,6 +5298,8 @@
cleanupRemoteTargets();
if (mRecentsAnimationController == null) {
+ Log.d(TestProtocol.INCORRECT_HOME_STATE, "finish recents animation but recents "
+ + "animation controller was null. returning.");
if (onFinishComplete != null) {
onFinishComplete.run();
}
@@ -5943,13 +6036,8 @@
}
@Nullable
- public FloatingTaskView getFirstFloatingTaskView() {
- return mFirstFloatingTaskView;
- }
-
- @Nullable
public SplitInstructionsView getSplitInstructionsView() {
- return mSplitInstructionsView;
+ return mSplitSelectStateController.getSplitInstructionsView();
}
/** Update the current activity locus id to show the enabled state of Overview */
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 0d9e412..4ca02e0 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -17,15 +17,21 @@
package com.android.quickstep.views;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.view.MotionEvent;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StatefulActivity;
/**
@@ -65,7 +71,7 @@
mLauncher = (StatefulActivity) context;
}
- static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
+ public static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
ViewGroup dragLayer = launcher.getDragLayer();
final SplitInstructionsView splitInstructionsView =
(SplitInstructionsView) launcher.getLayoutInflater().inflate(
@@ -73,9 +79,7 @@
dragLayer,
false
);
-
- splitInstructionsView.mTextView = splitInstructionsView.findViewById(
- R.id.split_instructions_text);
+ splitInstructionsView.init();
// Since textview overlays base view, and we sometimes manipulate the alpha of each
// simultaneously, force overlapping rendering to false prevents redrawing of pixels,
@@ -92,6 +96,71 @@
ensureProperRotation();
}
+ private void init() {
+ mTextView = findViewById(R.id.split_instructions_text);
+
+ if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ mTextView.setCompoundDrawables(null, null, null, null);
+ return;
+ }
+
+ mTextView.setOnTouchListener((v, event) -> {
+ if (isTouchInsideRightCompoundDrawable(event)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ exitSplitSelection();
+ }
+ return true;
+ }
+ return false;
+ });
+ }
+
+ private void exitSplitSelection() {
+ ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController()
+ .getSplitAnimationController().playPlaceholderDismissAnim(mLauncher);
+ mLauncher.getStateManager().goToState(LauncherState.NORMAL);
+ }
+
+ private boolean isTouchInsideRightCompoundDrawable(MotionEvent event) {
+ // Get the right compound drawable of the TextView.
+ Drawable rightDrawable = mTextView.getCompoundDrawablesRelative()[2];
+
+ // Check if the touch event intersects with the drawable's bounds.
+ if (rightDrawable != null) {
+ // We can get away w/o caring about the Y bounds since it's such a small view, if it's
+ // above/below the drawable just assume they meant to touch it. ¯\_(ツ)_/¯
+ return event.getX() >= (mTextView.getWidth() - rightDrawable.getBounds().width());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ return;
+ }
+
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+ R.string.toast_split_select_cont_desc,
+ getResources().getString(R.string.toast_split_select_cont_desc)
+ ));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ return super.performAccessibilityAction(action, arguments);
+ }
+
+ if (action == R.string.toast_split_select_cont_desc) {
+ exitSplitSelection();
+ return true;
+ }
+ return super.performAccessibilityAction(action, arguments);
+ }
+
void ensureProperRotation() {
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler()
.setSplitInstructionsParams(
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 2c9afb4..b4d24e5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -36,12 +36,12 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.touch.PagedOrientationHandler;
@@ -256,7 +256,7 @@
final Animator revealAnimator = createOpenCloseOutlineProvider()
.createRevealAnimator(this, closing);
- revealAnimator.setInterpolator(Interpolators.DEACCEL);
+ revealAnimator.setInterpolator(Interpolators.DECELERATE);
mOpenCloseAnimator.playTogether(revealAnimator,
ObjectAnimator.ofFloat(
mTaskContainer.getThumbnailView(), DIM_ALPHA,
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 83a5c72..a2976a8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -20,11 +20,11 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.widget.Toast.LENGTH_SHORT;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -32,6 +32,7 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
+import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts;
import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -70,12 +71,13 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
@@ -88,6 +90,7 @@
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
@@ -95,7 +98,6 @@
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskIconCache;
-import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TaskViewUtils;
@@ -132,15 +134,17 @@
public static final int FLAG_UPDATE_ICON = 1;
public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
+ public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1;
- public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL;
+ public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL
+ | FLAG_UPDATE_CORNER_RADIUS;
/**
* Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
* granularity on which components of this task require an update
*/
@Retention(SOURCE)
- @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
+ @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS})
public @interface TaskDataChanges {}
/**
@@ -163,7 +167,7 @@
public static final long SCALE_ICON_DURATION = 120;
private static final long DIM_ANIM_DURATION = 700;
- private static final Interpolator GRID_INTERPOLATOR = ACCEL_DEACCEL;
+ private static final Interpolator GRID_INTERPOLATOR = ACCELERATE_DECELERATE;
/**
* This technically can be a vanilla {@link TouchDelegate} class, however that class requires
@@ -411,7 +415,9 @@
private boolean mIsClickableAsLiveTile = true;
- @Nullable private final BorderAnimator mBorderAnimator;
+ @Nullable private final BorderAnimator mFocusBorderAnimator;
+
+ @Nullable private final BorderAnimator mHoverBorderAnimator;
public TaskView(Context context) {
this(context, null);
@@ -437,23 +443,40 @@
boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|| DesktopTaskView.DESKTOP_MODE_SUPPORTED;
- setWillNotDraw(!keyboardFocusHighlightEnabled);
+ boolean willDrawBorder =
+ keyboardFocusHighlightEnabled || FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get();
+ setWillNotDraw(!willDrawBorder);
- TypedArray ta = context.obtainStyledAttributes(
- attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
+ if (willDrawBorder) {
+ TypedArray styledAttrs = context.obtainStyledAttributes(
+ attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
- mBorderAnimator = !keyboardFocusHighlightEnabled
- ? null
- : new BorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderColor= */ ta.getColor(
- R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
- /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this));
- ta.recycle();
+ mFocusBorderAnimator = keyboardFocusHighlightEnabled ? new BorderAnimator(
+ /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR),
+ /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this)) : null;
+
+ mHoverBorderAnimator =
+ FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get() ? new BorderAnimator(
+ /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR),
+ /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
+ /* borderWidthPx= */ context.getResources()
+ .getDimensionPixelSize(R.dimen.task_hover_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this)) : null;
+
+ styledAttrs.recycle();
+ } else {
+ mFocusBorderAnimator = null;
+ mHoverBorderAnimator = null;
+ }
}
protected void updateBorderBounds(Rect bounds) {
@@ -507,16 +530,48 @@
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mBorderAnimator != null) {
- mBorderAnimator.buildAnimator(gainFocus).start();
+ if (mFocusBorderAnimator != null) {
+ mFocusBorderAnimator.buildAnimator(gainFocus).start();
+ }
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get()) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ mHoverBorderAnimator.buildAnimator(/* isAppearing= */ true).start();
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ mHoverBorderAnimator.buildAnimator(/* isAppearing= */ false).start();
+ break;
+ default:
+ break;
+ }
+ }
+ return super.onHoverEvent(event);
+ }
+
+ @Override
+ public boolean onInterceptHoverEvent(MotionEvent event) {
+ if (FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get()) {
+ // avoid triggering hover event on child elements which would cause HOVER_EXIT for this
+ // task view
+ return true;
+ } else {
+ return super.onInterceptHoverEvent(event);
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
- if (mBorderAnimator != null) {
- mBorderAnimator.drawBorder(canvas);
+ if (mFocusBorderAnimator != null) {
+ mFocusBorderAnimator.drawBorder(canvas);
+ }
+
+ if (mHoverBorderAnimator != null) {
+ mHoverBorderAnimator.drawBorder(canvas);
}
}
@@ -580,7 +635,6 @@
mIconView, STAGE_POSITION_UNDEFINED);
mSnapshotView.bind(task);
setOrientationState(orientedState);
- mDigitalWellBeingToast.initialize(mTask);
}
/**
@@ -1028,6 +1082,9 @@
mDigitalWellBeingToast.initialize(task);
});
}
+ if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
+ mCurrentFullscreenParams.updateCornerRadius(getContext());
+ }
} else {
if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
mSnapshotView.setThumbnail(null, null);
@@ -1563,8 +1620,8 @@
if (taskContainer == null) {
continue;
}
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
- taskContainer)) {
+ for (SystemShortcut s : TraceHelper.allowIpcs(
+ "TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) {
info.addAction(s.createAccessibilityAction(context));
}
}
@@ -1601,7 +1658,7 @@
if (taskContainer == null) {
continue;
}
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ for (SystemShortcut s : getEnabledShortcuts(this,
taskContainer)) {
if (s.hasHandlerForAction(action)) {
s.onClick(this);
@@ -1656,10 +1713,15 @@
}
void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
+ updateFullscreenParams(mCurrentFullscreenParams, previewPositionHelper);
+ }
+
+ protected void updateFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams,
+ PreviewPositionHelper previewPositionHelper) {
if (getRecentsView() == null) {
return;
}
- mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
+ fullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper);
}
@@ -1803,16 +1865,29 @@
*/
public static class FullscreenDrawParams {
- private final float mCornerRadius;
- private final float mWindowCornerRadius;
+ private float mCornerRadius;
+ private float mWindowCornerRadius;
public float mCurrentDrawnCornerRadius;
public FullscreenDrawParams(Context context) {
- mCornerRadius = TaskCornerRadius.get(context);
- mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context);
+ updateCornerRadius(context);
+ }
- mCurrentDrawnCornerRadius = mCornerRadius;
+ /** Recomputes the start and end corner radius for the given Context. */
+ public void updateCornerRadius(Context context) {
+ mCornerRadius = computeTaskCornerRadius(context);
+ mWindowCornerRadius = computeWindowCornerRadius(context);
+ }
+
+ @VisibleForTesting
+ public float computeTaskCornerRadius(Context context) {
+ return TaskCornerRadius.get(context);
+ }
+
+ @VisibleForTesting
+ public float computeWindowCornerRadius(Context context) {
+ return QuickStepContract.getWindowCornerRadius(context);
}
/**
diff --git a/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt b/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
new file mode 100644
index 0000000..a532762
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
@@ -0,0 +1,127 @@
+package com.android.launcher3.model
+
+import android.app.prediction.AppPredictor
+import android.app.prediction.AppTarget
+import android.app.prediction.AppTargetEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
+import com.android.launcher3.util.LauncherModelHelper
+import org.junit.After
+import org.junit.Assert.assertNotSame
+import org.junit.Assert.assertSame
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+/** Unit tests for [QuickstepModelDelegate]. */
+@RunWith(AndroidJUnit4::class)
+class QuickstepModelDelegateTest {
+
+ private lateinit var underTest: QuickstepModelDelegate
+ private lateinit var modelHelper: LauncherModelHelper
+
+ @Mock private lateinit var target: AppTarget
+ @Mock private lateinit var mockedAppTargetEvent: AppTargetEvent
+ @Mock private lateinit var allAppsPredictor: AppPredictor
+ @Mock private lateinit var hotseatPredictor: AppPredictor
+ @Mock private lateinit var widgetRecommendationPredictor: AppPredictor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ modelHelper = LauncherModelHelper()
+ underTest = QuickstepModelDelegate(modelHelper.sandboxContext)
+ underTest.mAllAppsState.predictor = allAppsPredictor
+ underTest.mHotseatState.predictor = hotseatPredictor
+ underTest.mWidgetsRecommendationState.predictor = widgetRecommendationPredictor
+ underTest.mApp = LauncherAppState.getInstance(modelHelper.sandboxContext)
+ underTest.mDataModel = BgDataModel()
+ }
+
+ @After
+ fun tearDown() {
+ modelHelper.destroy()
+ }
+
+ @Test
+ fun onAppTargetEvent_notifyTarget() {
+ underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_PREDICTION)
+
+ verify(allAppsPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+ verifyZeroInteractions(hotseatPredictor)
+ verifyZeroInteractions(widgetRecommendationPredictor)
+ }
+
+ @Test
+ fun onWidgetPrediction_notifyWidgetRecommendationPredictor() {
+ underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WIDGETS_PREDICTION)
+
+ verifyZeroInteractions(allAppsPredictor)
+ verify(widgetRecommendationPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+ verifyZeroInteractions(hotseatPredictor)
+ }
+
+ @Test
+ fun onHotseatPrediction_notifyHotseatPredictor() {
+ underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_HOTSEAT_PREDICTION)
+
+ verifyZeroInteractions(allAppsPredictor)
+ verifyZeroInteractions(widgetRecommendationPredictor)
+ verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+ }
+
+ @Test
+ fun onOtherClient_notifyHotseatPredictor() {
+ underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WALLPAPERS)
+
+ verifyZeroInteractions(allAppsPredictor)
+ verifyZeroInteractions(widgetRecommendationPredictor)
+ verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+ }
+
+ @Test
+ fun hotseatActionPin_recreateHotSeat() {
+ assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+ val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_PIN).build()
+ underTest.markActive()
+
+ underTest.onAppTargetEvent(appTargetEvent, CONTAINER_HOTSEAT_PREDICTION)
+
+ verify(hotseatPredictor).destroy()
+ assertNotSame(underTest.mHotseatState.predictor, hotseatPredictor)
+ }
+
+ @Test
+ fun hotseatActionUnpin_recreateHotSeat() {
+ assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+ underTest.markActive()
+ val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_UNPIN).build()
+
+ underTest.onAppTargetEvent(appTargetEvent, CONTAINER_HOTSEAT_PREDICTION)
+
+ verify(hotseatPredictor).destroy()
+ assertNotSame(underTest.mHotseatState.predictor, hotseatPredictor)
+ }
+
+ @Test
+ fun container_actionPin_notRecreateHotSeat() {
+ assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+ val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_UNPIN).build()
+ underTest.markActive()
+
+ underTest.onAppTargetEvent(appTargetEvent, CONTAINER_PREDICTION)
+
+ verify(allAppsPredictor, never()).destroy()
+ verify(hotseatPredictor, never()).destroy()
+ assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+ }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index 83341cb..b12d98b 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat;
@@ -40,11 +41,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
+import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -53,8 +52,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.List;
@@ -76,17 +73,9 @@
private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle;
- @Mock
- private IconCache mIconCache;
-
@Before
public void setup() throws Exception {
mModelHelper = new LauncherModelHelper();
- MockitoAnnotations.initMocks(this);
- doAnswer(invocation -> {
- ComponentWithLabel componentWithLabel = invocation.getArgument(0);
- return componentWithLabel.getComponent().getShortClassName();
- }).when(mIconCache).getTitleNoCache(any());
mUserHandle = myUserHandle();
mApp1Provider1 = createAppWidgetProviderInfo(
@@ -114,16 +103,12 @@
.collect(Collectors.toList());
}).when(manager).getInstalledProvidersForPackage(any(), eq(myUserHandle()));
- // 2 widgets, app4/provider1 & app5/provider1, have already been added to the workspace.
- mModelHelper.initializeData("widgets_predication_update_task_data");
-
+ LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+ .atWorkspace(0, 1, 2).putWidget("app4", "provider1", 1, 1)
+ .atWorkspace(0, 1, 3).putWidget("app5", "provider1", 1, 1);
+ mModelHelper.setupDefaultLayoutProvider(builder);
MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(mCallback)).get();
- MODEL_EXECUTOR.post(() -> mModelHelper.getBgDataModel().widgetsModel.update(
- LauncherAppState.getInstance(mModelHelper.sandboxContext),
- /* packageUser= */ null));
-
- MODEL_EXECUTOR.submit(() -> { }).get();
- MAIN_EXECUTOR.submit(() -> { }).get();
+ mModelHelper.loadModelSync();
}
@After
@@ -132,65 +117,72 @@
}
@Test
- public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder()
- throws Exception {
- // WHEN newPredicationTask is executed with app predication of 5 apps.
- AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
- mUserHandle);
- AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "provider1",
- mUserHandle);
- AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
- mUserHandle);
- AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
- mUserHandle);
- AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
- mUserHandle);
- mModelHelper.executeTaskForTest(
- newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)))
- .forEach(Runnable::run);
+ public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ // WHEN newPredicationTask is executed with app predication of 5 apps.
+ AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
+ mUserHandle);
+ AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "provider1",
+ mUserHandle);
+ AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
+ mUserHandle);
+ AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
+ mUserHandle);
+ AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
+ mUserHandle);
+ mCallback.mRecommendedWidgets = null;
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)));
+ runOnExecutorSync(MAIN_EXECUTOR, () -> { });
- // THEN only 2 widgets are returned because
- // 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
- // excluded from the result.
- // 2. app3 doesn't have a widget.
- // 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
- List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
- .stream()
- .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
- .collect(Collectors.toList());
- assertThat(recommendedWidgets).hasSize(2);
- assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
- assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
+ // THEN only 2 widgets are returned because
+ // 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
+ // excluded from the result.
+ // 2. app3 doesn't have a widget.
+ // 3. only 1 widget is picked from app1 because we only want to promote one widget per app.
+ List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
+ .stream()
+ .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
+ .collect(Collectors.toList());
+ assertThat(recommendedWidgets).hasSize(2);
+ assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
+ assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
+ });
}
@Test
- public void widgetsRecommendationRan_shouldReturnPackageWidgetsWhenEmpty()
- throws Exception {
+ public void widgetsRecommendationRan_shouldReturnPackageWidgetsWhenEmpty() {
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
- // Not installed widget
- AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider3",
- mUserHandle);
- // Not installed app
- AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1",
- mUserHandle);
- // Workspace added widgets
- AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
- mUserHandle);
- AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
- mUserHandle);
- mModelHelper.executeTaskForTest(
- newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)))
- .forEach(Runnable::run);
+ // Not installed widget
+ AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider3",
+ mUserHandle);
+ // Not installed app
+ AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1",
+ mUserHandle);
+ // Workspace added widgets
+ AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
+ mUserHandle);
+ AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
+ mUserHandle);
- // THEN only 2 widgets are returned because the launcher only filters out non-exist widgets.
- List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
- .stream()
- .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
- .collect(Collectors.toList());
- assertThat(recommendedWidgets).hasSize(2);
- // Another widget from the same package
- assertWidgetInfo(recommendedWidgets.get(0).info, mApp4Provider2);
- assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
+ mCallback.mRecommendedWidgets = null;
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)));
+ runOnExecutorSync(MAIN_EXECUTOR, () -> { });
+
+ // THEN only 2 widgets are returned because the launcher only filters out
+ // non-exist widgets.
+ List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
+ .stream()
+ .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
+ .collect(Collectors.toList());
+ assertThat(recommendedWidgets).hasSize(2);
+ // Another widget from the same package
+ assertWidgetInfo(recommendedWidgets.get(0).info, mApp4Provider2);
+ assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
+ });
}
private void assertWidgetInfo(
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
new file mode 100644
index 0000000..6c0d44d
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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.taskbar;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
+import static com.android.launcher3.taskbar.TaskbarHoverToolTipController.HOVER_TOOL_TIP_REVEAL_START_DELAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.util.ActivityContextWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Tests for TaskbarHoverToolTipController.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
+
+ private TaskbarHoverToolTipController mTaskbarHoverToolTipController;
+ private TestableLooper mTestableLooper;
+
+ @Mock private TaskbarView mTaskbarView;
+ @Mock private MotionEvent mMotionEvent;
+ @Mock private BubbleTextView mHoverBubbleTextView;
+ @Mock private FolderIcon mHoverFolderIcon;
+ @Mock private Display mDisplay;
+ @Mock private TaskbarDragLayer mTaskbarDragLayer;
+ private Folder mSpyFolderView;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = getApplicationContext();
+
+ doAnswer((Answer<Object>) invocation -> context.getSystemService(
+ (String) invocation.getArgument(0)))
+ .when(taskbarActivityContext).getSystemService(anyString());
+ when(taskbarActivityContext.getResources()).thenReturn(context.getResources());
+ when(taskbarActivityContext.getApplicationInfo()).thenReturn(
+ context.getApplicationInfo());
+ when(taskbarActivityContext.getDragLayer()).thenReturn(mTaskbarDragLayer);
+ when(taskbarActivityContext.getMainLooper()).thenReturn(context.getMainLooper());
+ when(taskbarActivityContext.getDisplay()).thenReturn(mDisplay);
+
+ when(mTaskbarDragLayer.getChildCount()).thenReturn(1);
+ mSpyFolderView = spy(new Folder(new ActivityContextWrapper(context), null));
+ when(mTaskbarDragLayer.getChildAt(anyInt())).thenReturn(mSpyFolderView);
+ doReturn(false).when(mSpyFolderView).isOpen();
+
+ when(mHoverBubbleTextView.getText()).thenReturn("tooltip");
+ doAnswer((Answer<Void>) invocation -> {
+ Object[] args = invocation.getArguments();
+ ((int[]) args[0])[0] = 0;
+ ((int[]) args[0])[1] = 0;
+ return null;
+ }).when(mHoverBubbleTextView).getLocationOnScreen(any(int[].class));
+ when(mHoverBubbleTextView.getWidth()).thenReturn(100);
+ when(mHoverBubbleTextView.getHeight()).thenReturn(100);
+
+ mHoverFolderIcon.mInfo = new FolderInfo();
+ mHoverFolderIcon.mInfo.title = "tooltip";
+ doAnswer((Answer<Void>) invocation -> {
+ Object[] args = invocation.getArguments();
+ ((int[]) args[0])[0] = 0;
+ ((int[]) args[0])[1] = 0;
+ return null;
+ }).when(mHoverFolderIcon).getLocationOnScreen(any(int[].class));
+ when(mHoverFolderIcon.getWidth()).thenReturn(100);
+ when(mHoverFolderIcon.getHeight()).thenReturn(100);
+
+ when(mTaskbarView.getTop()).thenReturn(200);
+
+ mTaskbarHoverToolTipController = new TaskbarHoverToolTipController(
+ taskbarActivityContext, mTaskbarView, mHoverBubbleTextView);
+ mTestableLooper = TestableLooper.get(this);
+ }
+
+ @Test
+ public void onHover_hoverEnterIcon_revealToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
+
+ // Verify fullscreen is not set until the delayed runnable to reveal the tooltip has run
+ verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(true);
+ waitForIdleSync();
+ assertThat(hoverHandled).isTrue();
+ verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
+ true);
+ verify(taskbarActivityContext).setTaskbarWindowFullscreen(true);
+ }
+
+ @Test
+ public void onHover_hoverExitIcon_closeToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
+ waitForIdleSync();
+
+ assertThat(hoverHandled).isTrue();
+ verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
+ false);
+ }
+
+ @Test
+ public void onHover_hoverEnterFolderIcon_revealToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
+
+ // Verify fullscreen is not set until the delayed runnable to reveal the tooltip has run
+ verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(true);
+ waitForIdleSync();
+ assertThat(hoverHandled).isTrue();
+ verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
+ true);
+ verify(taskbarActivityContext).setTaskbarWindowFullscreen(true);
+ }
+
+ @Test
+ public void onHover_hoverExitFolderIcon_closeToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
+ waitForIdleSync();
+
+ assertThat(hoverHandled).isTrue();
+ verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
+ false);
+ }
+
+ @Test
+ public void onHover_hoverExitFolderOpen_closeToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
+ doReturn(true).when(mSpyFolderView).isOpen();
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
+ waitForIdleSync();
+
+ assertThat(hoverHandled).isTrue();
+ verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
+ false);
+ }
+
+ @Test
+ public void onHover_hoverEnterFolderOpen_noToolTip() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+ doReturn(true).when(mSpyFolderView).isOpen();
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
+
+ assertThat(hoverHandled).isFalse();
+ }
+
+ @Test
+ public void onHover_hoverMove_noUpdate() {
+ when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
+ when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
+
+ boolean hoverHandled =
+ mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
+
+ assertThat(hoverHandled).isFalse();
+ }
+
+ private void waitForIdleSync() {
+ mTestableLooper.moveTimeForward(HOVER_TOOL_TIP_REVEAL_START_DELAY + 1);
+ mTestableLooper.processAllMessages();
+ }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index 9622619..58be345 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -17,6 +17,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -26,12 +27,14 @@
import android.os.Handler;
import android.view.View;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.logging.StatsLogManager;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.AssistUtils;
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +56,8 @@
@Mock
Handler mockHandler;
@Mock
+ AssistUtils mockAssistUtils;
+ @Mock
StatsLogManager mockStatsLogManager;
@Mock
StatsLogManager.StatsLogger mockStatsLogger;
@@ -70,12 +75,15 @@
MockitoAnnotations.initMocks(this);
when(mockService.getDisplayId()).thenReturn(DISPLAY_ID);
when(mockService.getOverviewCommandHelper()).thenReturn(mockCommandHelper);
+ when(mockService.getApplicationContext())
+ .thenReturn(InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getApplicationContext());
when(mockStatsLogManager.logger()).thenReturn(mockStatsLogger);
when(mockTaskbarControllers.getTaskbarActivityContext())
.thenReturn(mockTaskbarActivityContext);
doReturn(mockStatsLogManager).when(mockTaskbarActivityContext).getStatsLogManager();
mNavButtonController = new TaskbarNavButtonController(mockService,
- mockSystemUiProxy, mockHandler);
+ mockSystemUiProxy, mockHandler, mockAssistUtils);
}
@Test
@@ -104,16 +112,42 @@
}
@Test
- public void testLongPressHome_enabled() {
+ public void testLongPressHome_enabled_withoutOverride() {
mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/);
+ when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false);
+
mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
+ verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt());
verify(mockSystemUiProxy, times(1)).startAssistant(any());
}
@Test
- public void testLongPressHome_disabled() {
- mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/);
+ public void testLongPressHome_enabled_withOverride() {
+ mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/);
+ when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true);
+
mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
+ verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt());
+ verify(mockSystemUiProxy, never()).startAssistant(any());
+ }
+
+ @Test
+ public void testLongPressHome_disabled_withoutOverride() {
+ mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/);
+ when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false);
+
+ mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
+ verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt());
+ verify(mockSystemUiProxy, never()).startAssistant(any());
+ }
+
+ @Test
+ public void testLongPressHome_disabled_withOverride() {
+ mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/);
+ when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true);
+
+ mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
+ verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt());
verify(mockSystemUiProxy, never()).startAssistant(any());
}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 236b5db..3920b08 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -1,6 +1,9 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.Rotation
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
@@ -32,6 +35,8 @@
@Mock lateinit var mockRecentsButton: ImageView
@Mock lateinit var mockHomeButton: ImageView
+ private var surfaceRotation = Surface.ROTATION_0
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -60,7 +65,8 @@
isKidsMode = true,
isInSetup = false,
isThreeButtonNav = false,
- phoneMode = false
+ phoneMode = false,
+ surfaceRotation = surfaceRotation
)
assert(layoutter is KidsNavLayoutter)
}
@@ -74,7 +80,8 @@
isKidsMode = false,
isInSetup = true,
isThreeButtonNav = false,
- phoneMode = false
+ phoneMode = false,
+ surfaceRotation = surfaceRotation
)
assert(layoutter is SetupNavLayoutter)
}
@@ -88,7 +95,8 @@
isKidsMode = false,
isInSetup = false,
isThreeButtonNav = false,
- phoneMode = false
+ phoneMode = false,
+ surfaceRotation = surfaceRotation
)
assert(layoutter is TaskbarNavLayoutter)
}
@@ -101,7 +109,8 @@
isKidsMode = false,
isInSetup = false,
isThreeButtonNav = false,
- phoneMode = false
+ phoneMode = false,
+ surfaceRotation = surfaceRotation
)
}
@@ -114,7 +123,8 @@
isKidsMode = false,
isInSetup = false,
isThreeButtonNav = true,
- phoneMode = true
+ phoneMode = true,
+ surfaceRotation = surfaceRotation
)
assert(layoutter is PhonePortraitNavLayoutter)
}
@@ -129,11 +139,28 @@
isKidsMode = false,
isInSetup = false,
isThreeButtonNav = true,
- phoneMode = true
+ phoneMode = true,
+ surfaceRotation = surfaceRotation
)
assert(layoutter is PhoneLandscapeNavLayoutter)
}
+ @Test
+ fun getTaskbarSeascapeLayoutter() {
+ assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+ mockDeviceProfile.isTaskbarPresent = false
+ setDeviceProfileLandscape()
+ val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
+ getLayoutter(
+ isKidsMode = false,
+ isInSetup = false,
+ isThreeButtonNav = true,
+ phoneMode = true,
+ surfaceRotation = ROTATION_270
+ )
+ assert(layoutter is PhoneSeascapeNavLayoutter)
+ }
+
@Test(expected = IllegalStateException::class)
fun noValidLayoutForPhoneGestureNav() {
assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
@@ -142,7 +169,8 @@
isKidsMode = false,
isInSetup = false,
isThreeButtonNav = false,
- phoneMode = true
+ phoneMode = true,
+ surfaceRotation = surfaceRotation
)
}
@@ -157,7 +185,8 @@
isKidsMode: Boolean,
isInSetup: Boolean,
isThreeButtonNav: Boolean,
- phoneMode: Boolean
+ phoneMode: Boolean,
+ @Rotation surfaceRotation: Int
): NavButtonLayoutFactory.NavButtonLayoutter {
return NavButtonLayoutFactory.getUiLayoutter(
deviceProfile = mockDeviceProfile,
@@ -166,7 +195,8 @@
isKidsMode = isKidsMode,
isInSetup = isInSetup,
isThreeButtonNav = isThreeButtonNav,
- phoneMode = phoneMode
+ phoneMode = phoneMode,
+ surfaceRotation = surfaceRotation
)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 59f9d5f..3a5fb04 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -16,11 +16,17 @@
package com.android.quickstep;
+import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+
import static org.junit.Assert.assertTrue;
import android.os.SystemProperties;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.Until;
+
import com.android.launcher3.Launcher;
+import com.android.launcher3.tapl.LaunchedAppState;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -76,6 +82,21 @@
}
}
+ protected void assertTestActivityIsRunning(int activityNumber, String message) {
+ assertTrue(message, mDevice.wait(
+ Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity" + activityNumber)),
+ DEFAULT_UI_TIMEOUT));
+ }
+
+ protected LaunchedAppState getAndAssertLaunchedApp() {
+ final LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
+ executeOnLauncher(launcher -> assertTrue(
+ "Launcher activity is the top activity; expecting another activity to be the top "
+ + "one",
+ isInLaunchedApp(launcher)));
+ return launchedAppState;
+ }
+
private boolean isInLiveTileMode(Launcher launcher,
LauncherInstrumentation.ContainerType expectedContainerType) {
if (expectedContainerType != LauncherInstrumentation.ContainerType.OVERVIEW) {
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 97e34c5..a67d787 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -116,12 +116,13 @@
Utilities.enableRunningInTestHarnessForTests();
}
- final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
+ final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
+ RecentsActivity.ACTIVITY_TRACKER::getCreatedActivity);
mOrderSensitiveRules = RuleChain
.outerRule(new SamplerRule())
.around(new NavigationModeSwitchRule(mLauncher))
- .around(viewCaptureRule)
- .around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
+ .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
+ .around(viewCaptureRule);
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
getHomeIntentInPackage(context),
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index a9dc043..bb1afdf 100644
--- a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.quickstep
+import android.content.Context
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -29,7 +30,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
/** Test for FullscreenDrawParams class. */
@SmallTest
@@ -186,4 +189,76 @@
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
}
+
+ @Test
+ fun setStartProgress_correctCornerRadiusForMultiDisplay() {
+ val display1Context = context
+ val display2Context = mock(Context::class.java)
+ val spyParams = spy(params)
+
+ val display1TaskRadius = TaskCornerRadius.get(display1Context)
+ val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context)
+ val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different.
+ val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different.
+ doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context)
+ doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context)
+
+ spyParams.updateCornerRadius(display1Context)
+ spyParams.setProgress(
+ /* fullscreenProgress= */ 0f,
+ /* parentScale= */ 1.0f,
+ /* taskViewScale= */ 1.0f,
+ /* unused previewWidth= */ -1,
+ /* unusedDp= */ null,
+ /* unused previewPositionHelper= */ null
+ )
+ assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1TaskRadius)
+
+ spyParams.updateCornerRadius(display2Context)
+ spyParams.setProgress(
+ /* fullscreenProgress= */ 0f,
+ /* parentScale= */ 1.0f,
+ /* taskViewScale= */ 1.0f,
+ /* unused previewWidth= */ -1,
+ /* unusedDp= */ null,
+ /* unused previewPositionHelper= */ null
+ )
+ assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2TaskRadius)
+ }
+
+ @Test
+ fun setFullProgress_correctCornerRadiusForMultiDisplay() {
+ val display1Context = context
+ val display2Context = mock(Context::class.java)
+ val spyParams = spy(params)
+
+ val display1TaskRadius = TaskCornerRadius.get(display1Context)
+ val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context)
+ val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different.
+ val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different.
+ doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context)
+ doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context)
+
+ spyParams.updateCornerRadius(display1Context)
+ spyParams.setProgress(
+ /* fullscreenProgress= */ 1.0f,
+ /* parentScale= */ 1.0f,
+ /* taskViewScale= */ 1.0f,
+ /* unused previewWidth= */ -1,
+ /* unusedDp= */ null,
+ /* unused previewPositionHelper= */ null
+ )
+ assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1WindowRadius)
+
+ spyParams.updateCornerRadius(display2Context)
+ spyParams.setProgress(
+ /* fullscreenProgress= */ 1.0f,
+ /* parentScale= */ 1.0f,
+ /* taskViewScale= */ 1.0f,
+ /* unused previewWidth= */ -1,
+ /* unusedDp= */ null,
+ /* unused previewPositionHelper= */ null
+ )
+ assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2WindowRadius)
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 9c240f0..298dd6c 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -53,6 +53,8 @@
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class OrientationTouchTransformerTest {
@@ -296,7 +298,7 @@
WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
doReturn(cachedDisplayInfo).when(wmProxy).getDisplayInfo(any());
doReturn(windowBounds).when(wmProxy).getRealBounds(any(), any());
- ArrayMap<CachedDisplayInfo, WindowBounds[]> internalDisplayBounds = new ArrayMap<>();
+ ArrayMap<CachedDisplayInfo, List<WindowBounds>> internalDisplayBounds = new ArrayMap<>();
doReturn(internalDisplayBounds).when(wmProxy).estimateInternalDisplayBounds(any());
return new DisplayController.Info(
getApplicationContext(), wmProxy, new ArrayMap<>());
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index df5303f..20aa410 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -16,15 +16,17 @@
package com.android.quickstep;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.RaceConditionReproducer;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,21 +47,9 @@
startTestActivity(2);
}
- private void runTest(String... eventSequence) {
- final RaceConditionReproducer eventProcessor = new RaceConditionReproducer(eventSequence);
-
- // Destroy Launcher activity.
- closeLauncherActivity();
-
- // The test action.
- eventProcessor.startIteration();
- mLauncher.goHome();
- eventProcessor.finishIteration();
- }
-
- @Ignore
@Test
@NavigationModeSwitch
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
public void testStressPressHome() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
@@ -70,9 +60,9 @@
}
}
- @Ignore
@Test
@NavigationModeSwitch
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
public void testStressSwipeToOverview() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 32eadce..25f90ca 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -16,7 +16,10 @@
package com.android.quickstep;
+import static com.android.launcher3.testing.shared.TestProtocol.FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
import static org.junit.Assert.assertEquals;
@@ -27,6 +30,7 @@
import android.content.Intent;
import android.platform.test.annotations.PlatinumTest;
+import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -42,9 +46,13 @@
import com.android.launcher3.tapl.Overview;
import com.android.launcher3.tapl.OverviewActions;
import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.tapl.OverviewTaskMenu;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import com.android.quickstep.views.RecentsView;
@@ -143,7 +151,7 @@
assertNotNull("overview.getCurrentTask() returned null (1)", task);
assertNotNull("OverviewTask.open returned null", task.open());
assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
- By.pkg(getAppPackageName()).text("TestActivity2")),
+ By.pkg(getAppPackageName()).text("TestActivity2")),
DEFAULT_UI_TIMEOUT));
executeOnLauncher(launcher -> assertTrue(
"Launcher activity is the top activity; expecting another activity to be the top "
@@ -177,7 +185,6 @@
@Test
@NavigationModeSwitch
@PortraitLandscape
- @ScreenRecord // b/195673272
@PlatinumTest(focusArea = "launcher")
public void testOverviewActions() throws Exception {
// Experimenting for b/165029151:
@@ -192,6 +199,19 @@
actionsView.clickAndDismissScreenshot();
}
+
+ @Test
+ public void testOverviewActionsMenu() throws Exception {
+ startTestAppsWithCheck();
+
+ OverviewTaskMenu menu = mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu();
+
+ assertNotNull("Tapping App info menu item returned null", menu.tapAppInfoMenuItem());
+ executeOnLauncher(launcher -> assertTrue(
+ "Launcher activity is the top activity; expecting another activity to be the top",
+ isInLaunchedApp(launcher)));
+ }
+
private int getCurrentOverviewPage(Launcher launcher) {
return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
}
@@ -237,16 +257,6 @@
isInState(() -> LauncherState.OVERVIEW));
}
- private LaunchedAppState getAndAssertLaunchedApp() {
- final LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
- assertNotNull("Launcher.getLaunchedApp() returned null", launchedAppState);
- executeOnLauncher(launcher -> assertTrue(
- "Launcher activity is the top activity; expecting another activity to be the top "
- + "one",
- isInLaunchedApp(launcher)));
- return launchedAppState;
- }
-
private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
if (toRight) {
@@ -288,22 +298,22 @@
startTestActivity(4);
quickSwitchToPreviousAppAndAssert(true /* toRight */);
- assertTrue("The first app we should have quick switched to is not running",
- isTestActivityRunning(3));
+ assertTestActivityIsRunning(3,
+ "The first app we should have quick switched to is not running");
quickSwitchToPreviousAppAndAssert(true /* toRight */);
if (mLauncher.getNavigationModel() == NavigationModel.THREE_BUTTON) {
// 3-button mode toggles between 2 apps, rather than going back further.
- assertTrue("Second quick switch should have returned to the first app.",
- isTestActivityRunning(4));
+ assertTestActivityIsRunning(4,
+ "Second quick switch should have returned to the first app.");
} else {
- assertTrue("The second app we should have quick switched to is not running",
- isTestActivityRunning(2));
+ assertTestActivityIsRunning(2,
+ "The second app we should have quick switched to is not running");
}
quickSwitchToPreviousAppAndAssert(false /* toRight */);
- assertTrue("The 2nd app we should have quick switched to is not running",
- isTestActivityRunning(3));
+ assertTestActivityIsRunning(3,
+ "The 2nd app we should have quick switched to is not running");
final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
launchedAppState.switchToOverview();
@@ -312,7 +322,7 @@
@Test
@ScreenRecord // b/242163205
@PlatinumTest(focusArea = "launcher")
- @TaskbarModeSwitch(mode = PERSISTENT)
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/286084688
public void testQuickSwitchToPreviousAppForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
startTestActivity(2);
@@ -328,17 +338,20 @@
// Quick-switch to the test app with swiping to right.
quickSwitchToPreviousAppAndAssert(true /* toRight */);
- assertTrue("The first app we should have quick switched to is not running",
- isTestActivityRunning(2));
+ assertTestActivityIsRunning(2,
+ "The first app we should have quick switched to is not running");
// Expect task bar visible when the launched app was the test activity.
launchedAppState = getAndAssertLaunchedApp();
- launchedAppState.assertTaskbarVisible();
- }
- private boolean isTestActivityRunning(int activityNumber) {
- return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName())
- .text("TestActivity" + activityNumber)),
- DEFAULT_UI_TIMEOUT);
+ Log.e(FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP,
+ "is Taskbar Transient : " + DisplayController.isTransientTaskbar(mTargetContext));
+ // TODO(b/286084688): Remove this branching check after test corruption is resolved.
+ // Branching this check because of test corruption.
+ if (DisplayController.isTransientTaskbar(mTargetContext)) {
+ launchedAppState.assertTaskbarHidden();
+ } else {
+ launchedAppState.assertTaskbarVisible();
+ }
}
@Test
@@ -348,8 +361,8 @@
public void testQuickSwitchFromHome() throws Exception {
startTestActivity(2);
mLauncher.goHome().quickSwitchToPreviousApp();
- assertTrue("The most recent task is not running after quick switching from home",
- isTestActivityRunning(2));
+ assertTestActivityIsRunning(2,
+ "The most recent task is not running after quick switching from home");
getAndAssertLaunchedApp();
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index e8cadab..92b598b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -15,25 +15,41 @@
*/
package com.android.quickstep;
+
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.content.Intent;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+@LargeTest
+@RunWith(AndroidJUnit4.class)
public class TaplTestsSplitscreen extends AbstractQuickStepTest {
private static final String CALCULATOR_APP_NAME = "Calculator";
private static final String CALCULATOR_APP_PACKAGE =
resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+ private static final String READ_DEVICE_CONFIG_PERMISSION =
+ "android.permission.READ_DEVICE_CONFIG";
+
@Override
@Before
public void setUp() throws Exception {
@@ -44,6 +60,8 @@
mLauncher.enableBlockTimeout(true);
mLauncher.showTaskbarIfHidden();
}
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ READ_DEVICE_CONFIG_PERMISSION);
}
@After
@@ -62,6 +80,7 @@
@Test
@PortraitLandscape
@TaskbarModeSwitch
+ @TestStabilityRule.Stability(flavors = PLATFORM_POSTSUBMIT | LOCAL) // b/295225524
public void testSplitAppFromHomeWithItself() throws Exception {
// Currently only tablets have Taskbar in Overview, so test is only active on tablets
assumeTrue(mLauncher.isTablet());
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index 40be480..4ff2f9c 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -20,6 +20,7 @@
import androidx.test.filters.LargeTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import org.junit.Test;
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
new file mode 100644
index 0000000..907dbcc
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
@@ -0,0 +1,132 @@
+/*
+ * 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.quickstep;
+
+import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
+import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsTrackpad extends AbstractQuickStepTest {
+
+ private static final String READ_DEVICE_CONFIG_PERMISSION =
+ "android.permission.READ_DEVICE_CONFIG";
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ TaplTestsLauncher3.initialize(this);
+ }
+
+ @After
+ public void tearDown() {
+ mLauncher.setTrackpadGestureType(TrackpadGestureType.NONE);
+ }
+
+ @Test
+ @PortraitLandscape
+ @NavigationModeSwitch
+ public void goHome() throws Exception {
+ assumeTrue(mLauncher.isTablet());
+
+ mLauncher.setTrackpadGestureType(TrackpadGestureType.THREE_FINGER);
+ startTestActivity(2);
+ mLauncher.goHome();
+ }
+
+ @Test
+ @PortraitLandscape
+ // TODO(b/291944684): Support back in 3-button mode. It requires triggering the logic to enable
+ // trackpad gesture back in SysUI. Normally it's triggered by the attachment of a trackpad. We
+ // need to figure out a way to emulate that in the test, or bypass the logic altogether.
+ @NavigationModeSwitch(mode = ZERO_BUTTON)
+ public void pressBack() throws Exception {
+ assumeTrue(mLauncher.isTablet());
+ assumeFalse(FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get());
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ try {
+ instrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ READ_DEVICE_CONFIG_PERMISSION);
+ mLauncher.setTrackpadGestureType(TrackpadGestureType.THREE_FINGER);
+
+ startTestActivity(2);
+ mLauncher.pressBack();
+ } finally {
+ instrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ @PortraitLandscape
+ @NavigationModeSwitch
+ public void switchToOverview() throws Exception {
+ assumeTrue(mLauncher.isTablet());
+
+ mLauncher.setTrackpadGestureType(TrackpadGestureType.THREE_FINGER);
+ startTestActivity(2);
+ mLauncher.goHome().switchToOverview();
+ }
+
+ @Test
+ @PortraitLandscape
+ @NavigationModeSwitch
+ public void testAllAppsFromHome() throws Exception {
+ assumeTrue(mLauncher.isTablet());
+
+ mLauncher.setTrackpadGestureType(TrackpadGestureType.TWO_FINGER);
+ assertNotNull("switchToAllApps() returned null",
+ mLauncher.getWorkspace().switchToAllApps());
+ }
+
+ @Test
+ @NavigationModeSwitch
+ @PortraitLandscape
+ public void testQuickSwitchFromHome() throws Exception {
+ assumeTrue(mLauncher.isTablet());
+
+ startTestActivity(2);
+ Workspace workspace = mLauncher.goHome();
+ mLauncher.setTrackpadGestureType(TrackpadGestureType.FOUR_FINGER);
+ workspace.quickSwitchToPreviousApp();
+ assertTestActivityIsRunning(2,
+ "The most recent task is not running after quick switching from home");
+ getAndAssertLaunchedApp();
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
index b58fe29..3869bf7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
-
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -64,4 +63,15 @@
throw new RuntimeException(e);
}
}
+
+ @Test
+ @TaskbarModeSwitch(mode = TRANSIENT)
+ public void testClickHoveredTaskbarToGoHome() {
+ try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
+ getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+ mLauncher.getLaunchedAppState().clickStashedTaskbarToGoHome();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 7e408a8..8cc8487 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -17,8 +17,8 @@
import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
@@ -32,7 +32,6 @@
import static org.mockito.Mockito.spy;
import android.appwidget.AppWidgetManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -42,14 +41,16 @@
import android.view.ViewConfiguration;
import android.widget.RemoteViews;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.LaunchedAppState;
import com.android.launcher3.testcomponent.ListViewService;
@@ -57,6 +58,7 @@
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -67,6 +69,7 @@
import org.mockito.stubbing.Answer;
import java.lang.reflect.Field;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntConsumer;
/**
@@ -84,9 +87,9 @@
@RunWith(AndroidJUnit4.class)
public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
- private ContentResolver mResolver;
private SparseArray<ViewConfiguration> mConfigMap;
private InitTracker mInitTracker;
+ private LauncherModel mModel;
@Before
public void setUp() throws Exception {
@@ -101,8 +104,8 @@
TaplTestsLauncher3.initialize(this);
- mResolver = mTargetContext.getContentResolver();
- LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+ mModel = LauncherAppState.getInstance(mTargetContext).getModel();
+ Executors.MODEL_EXECUTOR.submit(mModel.getModelDbController()::createEmptyDB).get();
// Get static configuration map
Field field = ViewConfiguration.class.getDeclaredField("sConfigurations");
@@ -182,26 +185,30 @@
private void executeSwipeUpTestWithWidget(IntConsumer widgetIdCreationCallback,
IntConsumer updateBeforeSwipeUp, String finalWidgetText) {
try {
- // Clear all existing data
- LauncherSettings.Settings.call(mResolver,
- LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- LauncherSettings.Settings.call(mResolver,
- LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
+ LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
+
// Make sure the widget is big enough to show a list of items
info.minSpanX = 2;
info.minSpanY = 2;
info.spanX = 2;
info.spanY = 2;
- LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
+ AtomicInteger widgetId = new AtomicInteger();
+ new FavoriteItemsTransaction(mTargetContext)
+ .addItem(() -> {
+ LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, true);
+ item.screenId = FIRST_SCREEN_ID;
+ widgetId.set(item.appWidgetId);
+ return item;
+ })
+ .commitAndLoadHome(mLauncher);
- addItemToScreen(item);
+
+
assertTrue("Widget is not present",
mLauncher.goHome().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
- int widgetId = item.appWidgetId;
// Verify widget id
- widgetIdCreationCallback.accept(widgetId);
+ widgetIdCreationCallback.accept(widgetId.get());
// Go to overview once so that all views are initialized and cached
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -214,7 +221,7 @@
LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
// Update widget
- updateBeforeSwipeUp.accept(widgetId);
+ updateBeforeSwipeUp.accept(widgetId.get());
launchedAppState.switchToOverview();
assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
new file mode 100644
index 0000000..fc767fa
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
@@ -0,0 +1,395 @@
+/*
+ * 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.quickstep.util
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.ui.AbstractLauncherUiTest
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_PENDINGINTENT_PENDINGINTENT
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_PENDINGINTENT_TASK
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_SHORTCUT_TASK
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_SINGLE_INTENT_FULLSCREEN
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_SINGLE_SHORTCUT_FULLSCREEN
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_SINGLE_TASK_FULLSCREEN
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_TASK_PENDINGINTENT
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_TASK_SHORTCUT
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SPLIT_TASK_TASK
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+class SplitSelectDataHolderTest {
+ private lateinit var splitSelectDataHolder: SplitSelectDataHolder
+
+ private val context: Context =
+ ContextWrapper(InstrumentationRegistry.getInstrumentation().targetContext)
+ private val sampleTaskInfo = RunningTaskInfo()
+ private val sampleTaskId = 10
+ private val sampleTaskId2 = 11
+ private val sampleUser = UserHandle(0)
+ private val sampleIntent = Intent()
+ private val sampleIntent2 = Intent()
+ private val sampleShortcut = Intent()
+ private val sampleShortcut2 = Intent()
+ private val sampleItemInfo = ItemInfo()
+ private val samplePackage =
+ AbstractLauncherUiTest.resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)
+
+ @Before
+ fun setup() {
+ splitSelectDataHolder = SplitSelectDataHolder(context)
+
+ sampleTaskInfo.taskId = sampleTaskId
+ sampleItemInfo.user = sampleUser
+ sampleIntent.setPackage(samplePackage)
+ sampleIntent2.setPackage(samplePackage)
+ sampleShortcut.setPackage(samplePackage)
+ sampleShortcut2.setPackage(samplePackage)
+ sampleShortcut.putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "sampleShortcut")
+ sampleShortcut2.putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "sampleShortcut2")
+ }
+
+ @Test
+ fun setInitialAsTask() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleTaskInfo,
+ STAGE_POSITION_TOP_OR_LEFT,
+ null,
+ null
+ )
+ assertTrue(splitSelectDataHolder.isSplitSelectActive())
+ }
+
+ @Test
+ fun setInitialAsIntent() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleIntent,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ assertTrue(splitSelectDataHolder.isSplitSelectActive())
+ }
+
+ @Test
+ fun setInitialAsIntentWithAlreadyRunningTask() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleIntent,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ sampleTaskId
+ )
+ assertTrue(splitSelectDataHolder.isSplitSelectActive())
+ }
+
+ @Test
+ fun setInitialAsShortcut() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleShortcut,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ assertTrue(splitSelectDataHolder.isSplitSelectActive())
+ }
+
+ @Test
+ fun setSecondAsTask() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleShortcut,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ splitSelectDataHolder.setSecondTask(sampleTaskId)
+ assertTrue(splitSelectDataHolder.isBothSplitAppsConfirmed())
+ }
+
+ @Test
+ fun setSecondAsIntent() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleTaskInfo,
+ STAGE_POSITION_TOP_OR_LEFT,
+ null,
+ null
+ )
+ splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+ assertTrue(splitSelectDataHolder.isBothSplitAppsConfirmed())
+ }
+
+ @Test
+ fun setSecondAsShortcut() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleIntent,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ splitSelectDataHolder.setSecondTask(sampleShortcut, sampleUser)
+ assertTrue(splitSelectDataHolder.isBothSplitAppsConfirmed())
+ }
+
+ @Test
+ fun generateLaunchData_Task_Task() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleTaskInfo,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null
+ )
+ splitSelectDataHolder.setSecondTask(sampleTaskId2)
+ val launchData = splitSelectDataHolder.getSplitLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_TASK_TASK)
+
+ // should contain a valid task ID for first app, and no intent or shortcut
+ assertNotEquals(launchData.initialTaskId, INVALID_TASK_ID)
+ assertNull(launchData.initialPendingIntent)
+ assertNull(launchData.initialShortcut)
+
+ // should contain a valid task ID for second app, and no intent or shortcut
+ assertNotEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondPendingIntent)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun generateLaunchData_Task_Intent() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleTaskInfo,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null
+ )
+ splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+ val launchData = splitSelectDataHolder.getSplitLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_TASK_PENDINGINTENT)
+
+ // should contain a valid task ID for first app, and no intent or shortcut
+ assertNotEquals(launchData.initialTaskId, INVALID_TASK_ID)
+ assertNull(launchData.initialPendingIntent)
+ assertNull(launchData.initialShortcut)
+
+ // should contain a valid intent for second app, and no task ID or shortcut
+ assertNotNull(launchData.secondPendingIntent)
+ assertEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun generateLaunchData_Task_Shortcut() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleTaskInfo,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null
+ )
+ splitSelectDataHolder.setSecondTask(sampleShortcut, sampleUser)
+ val launchData = splitSelectDataHolder.getSplitLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_TASK_SHORTCUT)
+
+ // should contain a valid task ID for first app, and no intent or shortcut
+ assertNotEquals(launchData.initialTaskId, INVALID_TASK_ID)
+ assertNull(launchData.initialPendingIntent)
+ assertNull(launchData.initialShortcut)
+
+ // should contain a valid shortcut and intent for second app, and no task ID
+ assertNotNull(launchData.secondShortcut)
+ assertNotNull(launchData.secondPendingIntent)
+ assertEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ }
+
+ @Test
+ fun generateLaunchData_Intent_Task() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleIntent,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ splitSelectDataHolder.setSecondTask(sampleTaskId)
+ val launchData = splitSelectDataHolder.getSplitLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_PENDINGINTENT_TASK)
+
+ // should contain a valid intent for first app, and no task ID or shortcut
+ assertNotNull(launchData.initialPendingIntent)
+ assertEquals(launchData.initialTaskId, INVALID_TASK_ID)
+ assertNull(launchData.initialShortcut)
+
+ // should contain a valid task ID for second app, and no intent or shortcut
+ assertNotEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondPendingIntent)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun generateLaunchData_Shortcut_Task() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleShortcut,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ splitSelectDataHolder.setSecondTask(sampleTaskId)
+ val launchData = splitSelectDataHolder.getSplitLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_SHORTCUT_TASK)
+
+ // should contain a valid shortcut and intent for first app, and no task ID
+ assertNotNull(launchData.initialShortcut)
+ assertNotNull(launchData.initialPendingIntent)
+ assertEquals(launchData.initialTaskId, INVALID_TASK_ID)
+
+ // should contain a valid task ID for second app, and no intent or shortcut
+ assertNotEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondPendingIntent)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun generateLaunchData_Intent_Intent() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleIntent,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ splitSelectDataHolder.setSecondTask(sampleIntent2, sampleUser)
+ val launchData = splitSelectDataHolder.getSplitLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_PENDINGINTENT_PENDINGINTENT)
+
+ // should contain a valid intent for first app, and no task ID or shortcut
+ assertNotNull(launchData.initialPendingIntent)
+ assertEquals(launchData.initialTaskId, INVALID_TASK_ID)
+ assertNull(launchData.initialShortcut)
+
+ // should contain a valid intent for second app, and no task ID or shortcut
+ assertNotNull(launchData.secondPendingIntent)
+ assertEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun generateLaunchData_Single_Task() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleTaskInfo,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null
+ )
+ val launchData = splitSelectDataHolder.getFullscreenLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_SINGLE_TASK_FULLSCREEN)
+
+ // should contain a valid task ID for first app, and no intent or shortcut
+ assertNotEquals(launchData.initialTaskId, INVALID_TASK_ID)
+ assertNull(launchData.initialPendingIntent)
+ assertNull(launchData.initialShortcut)
+
+ // should contain no task ID, intent, or shortcut for second app
+ assertEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondPendingIntent)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun generateLaunchData_Single_Intent() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleIntent,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ val launchData = splitSelectDataHolder.getFullscreenLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_SINGLE_INTENT_FULLSCREEN)
+
+ // should contain a valid intent for first app, and no task ID or shortcut
+ assertNotNull(launchData.initialPendingIntent)
+ assertEquals(launchData.initialTaskId, INVALID_TASK_ID)
+ assertNull(launchData.initialShortcut)
+
+ // should contain no task ID, intent, or shortcut for second app
+ assertEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondPendingIntent)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun generateLaunchData_Single_Shortcut() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleShortcut,
+ STAGE_POSITION_TOP_OR_LEFT,
+ sampleItemInfo,
+ null,
+ INVALID_TASK_ID
+ )
+ val launchData = splitSelectDataHolder.getFullscreenLaunchData()
+
+ assertEquals(launchData.splitLaunchType, SPLIT_SINGLE_SHORTCUT_FULLSCREEN)
+
+ // should contain a valid shortcut and intent for first app, and no task ID
+ assertNotNull(launchData.initialShortcut)
+ assertNotNull(launchData.initialPendingIntent)
+ assertEquals(launchData.initialTaskId, INVALID_TASK_ID)
+
+ // should contain no task ID, intent, or shortcut for second app
+ assertEquals(launchData.secondTaskId, INVALID_TASK_ID)
+ assertNull(launchData.secondPendingIntent)
+ assertNull(launchData.secondShortcut)
+ }
+
+ @Test
+ fun clearState() {
+ splitSelectDataHolder.setInitialTaskSelect(
+ sampleTaskInfo,
+ STAGE_POSITION_TOP_OR_LEFT,
+ null,
+ null
+ )
+ splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+ splitSelectDataHolder.resetState()
+ assertFalse(splitSelectDataHolder.isSplitSelectActive())
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 65542cf..69109c2 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -37,6 +37,7 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.systemui.shared.recents.model.Task
+import java.util.function.Consumer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -48,7 +49,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
class SplitSelectStateControllerTest {
@@ -67,6 +67,9 @@
private val primaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId)
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
+ private var taskIdCounter = 0
+ private fun getUniqueId(): Int { return ++taskIdCounter }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -100,15 +103,15 @@
tasks.add(groupTask2)
// Assertions happen in the callback we get from what we pass into
- // #findLastActiveTaskAndRunCallback
+ // #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<Task> { assertNull("No tasks should have matched", it /*task*/) }
+ Consumer<List<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
// Capture callback from recentsModel#getTasks()
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTaskAndRunCallback(
- nonMatchingComponent,
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonMatchingComponent),
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -139,27 +142,27 @@
tasks.add(groupTask2)
// Assertions happen in the callback we get from what we pass into
- // #findLastActiveTaskAndRunCallback
+ // #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<Task> {
+ Consumer<List<Task>> {
assertEquals(
"ComponentName package mismatched",
- it.key.baseIntent.component.packageName,
+ it[0].key.baseIntent.component?.packageName,
matchingPackage
)
assertEquals(
"ComponentName class mismatched",
- it.key.baseIntent.component.className,
+ it[0].key.baseIntent.component?.className,
matchingClass
)
- assertEquals(it, groupTask1.task1)
+ assertEquals(it[0], groupTask1.task1)
}
// Capture callback from recentsModel#getTasks()
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTaskAndRunCallback(
- matchingComponent,
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent),
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -190,15 +193,15 @@
tasks.add(groupTask2)
// Assertions happen in the callback we get from what we pass into
- // #findLastActiveTaskAndRunCallback
+ // #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<Task> { assertNull("No tasks should have matched", it /*task*/) }
+ Consumer<List<Task>> { assertNull("No tasks should have matched", it[0] /*task*/) }
// Capture callback from recentsModel#getTasks()
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTaskAndRunCallback(
- nonPrimaryUserComponent,
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonPrimaryUserComponent),
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -231,28 +234,28 @@
tasks.add(groupTask2)
// Assertions happen in the callback we get from what we pass into
- // #findLastActiveTaskAndRunCallback
+ // #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<Task> {
+ Consumer<List<Task>> {
assertEquals(
"ComponentName package mismatched",
- it.key.baseIntent.component.packageName,
+ it[0].key.baseIntent.component?.packageName,
matchingPackage
)
assertEquals(
"ComponentName class mismatched",
- it.key.baseIntent.component.className,
+ it[0].key.baseIntent.component?.className,
matchingClass
)
- assertEquals("userId mismatched", it.key.userId, nonPrimaryUserHandle.identifier)
- assertEquals(it, groupTask1.task1)
+ assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
+ assertEquals(it[0], groupTask1.task1)
}
// Capture callback from recentsModel#getTasks()
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTaskAndRunCallback(
- nonPrimaryUserComponent,
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonPrimaryUserComponent),
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -283,27 +286,200 @@
tasks.add(groupTask1)
// Assertions happen in the callback we get from what we pass into
- // #findLastActiveTaskAndRunCallback
+ // #findLastActiveTasksAndRunCallback
val taskConsumer =
- Consumer<Task> {
+ Consumer<List<Task>> {
assertEquals(
"ComponentName package mismatched",
- it.key.baseIntent.component.packageName,
+ it[0].key.baseIntent.component?.packageName,
matchingPackage
)
assertEquals(
"ComponentName class mismatched",
- it.key.baseIntent.component.className,
+ it[0].key.baseIntent.component?.className,
matchingClass
)
- assertEquals(it, groupTask2.task2)
+ assertEquals(it[0], groupTask1.task1)
}
// Capture callback from recentsModel#getTasks()
val consumer =
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTaskAndRunCallback(
- matchingComponent,
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+
+ // Send our mocked tasks
+ consumer.accept(tasks)
+ }
+
+ @Test
+ fun activeTasks_multipleSearchShouldFindTask() {
+ val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
+ val matchingPackage = "hotdog"
+ val matchingClass = "juice"
+ val matchingComponent =
+ ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
+
+ val groupTask1 =
+ generateGroupTask(
+ ComponentName("hotdog", "pie"),
+ ComponentName("pumpkin", "pie")
+ )
+ val groupTask2 =
+ generateGroupTask(
+ ComponentName("pomegranate", "juice"),
+ ComponentName(matchingPackage, matchingClass)
+ )
+ val tasks: ArrayList<GroupTask> = ArrayList()
+ tasks.add(groupTask2)
+ tasks.add(groupTask1)
+
+ // Assertions happen in the callback we get from what we pass into
+ // #findLastActiveTasksAndRunCallback
+ val taskConsumer =
+ Consumer<List<Task>> {
+ assertEquals("Expected array length 2", 2, it.size)
+ assertNull("No tasks should have matched", it[0] /*task*/)
+ assertEquals(
+ "ComponentName package mismatched",
+ it[1].key.baseIntent.component?.packageName,
+ matchingPackage
+ )
+ assertEquals(
+ "ComponentName class mismatched",
+ it[1].key.baseIntent.component?.className,
+ matchingClass
+ )
+ assertEquals(it[1], groupTask2.task2)
+ }
+
+ // Capture callback from recentsModel#getTasks()
+ val consumer =
+ withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonMatchingComponent, matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+
+ // Send our mocked tasks
+ consumer.accept(tasks)
+ }
+
+ @Test
+ fun activeTasks_multipleSearchShouldNotFindSameTaskTwice() {
+ val matchingPackage = "hotdog"
+ val matchingClass = "juice"
+ val matchingComponent =
+ ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
+
+ val groupTask1 =
+ generateGroupTask(
+ ComponentName("hotdog", "pie"),
+ ComponentName("pumpkin", "pie")
+ )
+ val groupTask2 =
+ generateGroupTask(
+ ComponentName("pomegranate", "juice"),
+ ComponentName(matchingPackage, matchingClass)
+ )
+ val tasks: ArrayList<GroupTask> = ArrayList()
+ tasks.add(groupTask2)
+ tasks.add(groupTask1)
+
+ // Assertions happen in the callback we get from what we pass into
+ // #findLastActiveTasksAndRunCallback
+ val taskConsumer =
+ Consumer<List<Task>> {
+ assertEquals("Expected array length 2", 2, it.size)
+ assertEquals(
+ "ComponentName package mismatched",
+ it[0].key.baseIntent.component?.packageName,
+ matchingPackage
+ )
+ assertEquals(
+ "ComponentName class mismatched",
+ it[0].key.baseIntent.component?.className,
+ matchingClass
+ )
+ assertEquals(it[0], groupTask2.task2)
+ assertNull("No tasks should have matched", it[1] /*task*/)
+ }
+
+ // Capture callback from recentsModel#getTasks()
+ val consumer =
+ withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent, matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+
+ // Send our mocked tasks
+ consumer.accept(tasks)
+ }
+
+ @Test
+ fun activeTasks_multipleSearchShouldFindDifferentInstancesOfSameTask() {
+ val matchingPackage = "hotdog"
+ val matchingClass = "juice"
+ val matchingComponent =
+ ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
+
+ val groupTask1 =
+ generateGroupTask(
+ ComponentName(matchingPackage, matchingClass),
+ ComponentName("pumpkin", "pie")
+ )
+ val groupTask2 =
+ generateGroupTask(
+ ComponentName("pomegranate", "juice"),
+ ComponentName(matchingPackage, matchingClass)
+ )
+ val tasks: ArrayList<GroupTask> = ArrayList()
+ tasks.add(groupTask2)
+ tasks.add(groupTask1)
+
+ // Assertions happen in the callback we get from what we pass into
+ // #findLastActiveTasksAndRunCallback
+ val taskConsumer =
+ Consumer<List<Task>> {
+ assertEquals("Expected array length 2", 2, it.size)
+ assertEquals(
+ "ComponentName package mismatched",
+ it[0].key.baseIntent.component?.packageName,
+ matchingPackage
+ )
+ assertEquals(
+ "ComponentName class mismatched",
+ it[0].key.baseIntent.component?.className,
+ matchingClass
+ )
+ assertEquals(it[0], groupTask1.task1)
+ assertEquals(
+ "ComponentName package mismatched",
+ it[1].key.baseIntent.component?.packageName,
+ matchingPackage
+ )
+ assertEquals(
+ "ComponentName class mismatched",
+ it[1].key.baseIntent.component?.className,
+ matchingClass
+ )
+ assertEquals(it[1], groupTask2.task2)
+ }
+
+ // Capture callback from recentsModel#getTasks()
+ val consumer =
+ withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent, matchingComponent),
taskConsumer
)
verify(recentsModel).getTasks(capture())
@@ -366,6 +542,7 @@
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = getUniqueId()
var intent = Intent()
intent.component = task1ComponentName
taskInfo.baseIntent = intent
@@ -373,6 +550,7 @@
val task2 = Task()
taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = getUniqueId()
intent = Intent()
intent.component = task2ComponentName
taskInfo.baseIntent = intent
@@ -393,6 +571,7 @@
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = getUniqueId()
// Apply custom userHandle1
taskInfo.userId = userHandle1.identifier
var intent = Intent()
@@ -401,6 +580,7 @@
task1.key = Task.TaskKey(taskInfo)
val task2 = Task()
taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = getUniqueId()
// Apply custom userHandle2
taskInfo.userId = userHandle2.identifier
intent = Intent()
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 83602be..a54dc2d 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -50,6 +50,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskViewSimulatorTest {
@@ -150,7 +153,7 @@
WindowBounds wm = new WindowBounds(
new Rect(0, 0, mDisplaySize.x, mDisplaySize.y),
mDisplayInsets);
- WindowBounds[] allBounds = new WindowBounds[4];
+ List<WindowBounds> allBounds = new ArrayList<>(4);
for (int i = 0; i < 4; i++) {
Rect boundsR = new Rect(wm.bounds);
Rect insetsR = new Rect(wm.insets);
@@ -158,7 +161,7 @@
RotationUtils.rotateRect(insetsR, RotationUtils.deltaRotation(rotation, i));
RotationUtils.rotateRect(boundsR, RotationUtils.deltaRotation(rotation, i));
boundsR.set(0, 0, Math.abs(boundsR.width()), Math.abs(boundsR.height()));
- allBounds[i] = new WindowBounds(boundsR, insetsR);
+ allBounds.add(new WindowBounds(boundsR, insetsR));
}
WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
@@ -166,7 +169,7 @@
doReturn(wm).when(wmProxy).getRealBounds(any(), any());
doReturn(NavigationMode.NO_BUTTON).when(wmProxy).getNavigationMode(any());
- ArrayMap<CachedDisplayInfo, WindowBounds[]> perDisplayBoundsCache =
+ ArrayMap<CachedDisplayInfo, List<WindowBounds>> perDisplayBoundsCache =
new ArrayMap<>();
perDisplayBoundsCache.put(cdi.normalize(), allBounds);
diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml
index 94ef06c..3d7ad2b 100644
--- a/res/anim-v33/shared_x_axis_activity_close_enter.xml
+++ b/res/anim-v33/shared_x_axis_activity_close_enter.xml
@@ -25,7 +25,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/standard_decelerate"
+ android:interpolator="@interpolator/standard_decelerate_interpolator"
android:startOffset="100"
android:duration="350" />
@@ -35,7 +35,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml
index 19eb09e..fb63602 100644
--- a/res/anim-v33/shared_x_axis_activity_close_exit.xml
+++ b/res/anim-v33/shared_x_axis_activity_close_exit.xml
@@ -24,7 +24,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/standard_accelerate"
+ android:interpolator="@interpolator/standard_accelerate_interpolator"
android:startOffset="0"
android:duration="100" />
@@ -34,7 +34,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml
index f699cec..cba74ba 100644
--- a/res/anim-v33/shared_x_axis_activity_open_enter.xml
+++ b/res/anim-v33/shared_x_axis_activity_open_enter.xml
@@ -25,7 +25,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/standard_decelerate"
+ android:interpolator="@interpolator/standard_decelerate_interpolator"
android:startOffset="100"
android:duration="350" />
@@ -35,7 +35,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml
index 85988ec..22e878d 100644
--- a/res/anim-v33/shared_x_axis_activity_open_exit.xml
+++ b/res/anim-v33/shared_x_axis_activity_open_exit.xml
@@ -24,7 +24,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/standard_accelerate"
+ android:interpolator="@interpolator/standard_accelerate_interpolator"
android:startOffset="0"
android:duration="100" />
@@ -34,7 +34,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
diff --git a/res/drawable-hdpi/workspace_bg.9.png b/res/drawable-hdpi/workspace_bg.9.png
deleted file mode 100755
index 1d82fd4..0000000
--- a/res/drawable-hdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/workspace_bg.9.png b/res/drawable-mdpi/workspace_bg.9.png
deleted file mode 100755
index 116ce44..0000000
--- a/res/drawable-mdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/workspace_bg.9.png b/res/drawable-xhdpi/workspace_bg.9.png
deleted file mode 100755
index b1b3b85..0000000
--- a/res/drawable-xhdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/workspace_bg.9.png b/res/drawable-xxhdpi/workspace_bg.9.png
deleted file mode 100755
index d47f6b2..0000000
--- a/res/drawable-xxhdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/workspace_bg.9.png b/res/drawable-xxxhdpi/workspace_bg.9.png
deleted file mode 100755
index 3281548..0000000
--- a/res/drawable-xxxhdpi/workspace_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml
index 8471cd4..1e7cff2 100644
--- a/res/drawable/all_apps_tabs_background.xml
+++ b/res/drawable/all_apps_tabs_background.xml
@@ -30,7 +30,7 @@
android:state_selected="false">
<shape android:shape="rectangle">
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
- <solid android:color="@color/all_apps_tabs_background" />
+ <solid android:color="@color/material_color_surface_bright" />
</shape>
</item>
@@ -39,7 +39,7 @@
android:state_selected="true">
<shape android:shape="rectangle">
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
- <solid android:color="@color/all_apps_tab_background_selected" />
+ <solid android:color="@color/material_color_primary" />
</shape>
</item>
</selector>
diff --git a/res/drawable/arrow_toast_rounded_background.xml b/res/drawable/arrow_toast_rounded_background.xml
index 1206ddd..d7d6255 100644
--- a/res/drawable/arrow_toast_rounded_background.xml
+++ b/res/drawable/arrow_toast_rounded_background.xml
@@ -14,6 +14,6 @@
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <solid android:color="@color/arrow_tip_view_bg" />
+ <solid android:color="?attr/arrowTipBackground" />
<corners android:radius="@dimen/dialogCornerRadius" />
</shape>
diff --git a/res/drawable/bottom_rounded_popup_ripple.xml b/res/drawable/bottom_rounded_popup_ripple.xml
deleted file mode 100644
index 739833a..0000000
--- a/res/drawable/bottom_rounded_popup_ripple.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="?android:attr/colorControlHighlight">
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <solid android:color="#FFFFFFFF"/>
- <corners android:bottomLeftRadius="@dimen/dialogCornerRadius"
- android:bottomRightRadius="@dimen/dialogCornerRadius"
- android:topLeftRadius="0dp"
- android:topRightRadius="0dp"/>
- </shape>
- </item>
-</ripple>
\ No newline at end of file
diff --git a/res/drawable/bubble_ic_overflow_button.xml b/res/drawable/bubble_ic_overflow_button.xml
new file mode 100644
index 0000000..475639e
--- /dev/null
+++ b/res/drawable/bubble_ic_overflow_button.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:fillColor="#1A73E8"
+ android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/quickstep/res/drawable/taskbar_divider_bg.xml b/res/drawable/ic_split_exit.xml
similarity index 61%
rename from quickstep/res/drawable/taskbar_divider_bg.xml
rename to res/drawable/ic_split_exit.xml
index 52e230d..d7e8b03 100644
--- a/quickstep/res/drawable/taskbar_divider_bg.xml
+++ b/res/drawable/ic_split_exit.xml
@@ -13,8 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid android:color="@color/taskbar_divider_background"/>
- <corners android:radius="1dp" />
-</shape>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="20dp"
+ android:tint="#000000"
+ android:viewportHeight="24"
+ android:viewportWidth="24"
+ android:width="20dp">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_wallpaper.xml b/res/drawable/ic_wallpaper.xml
deleted file mode 100644
index 9543f88..0000000
--- a/res/drawable/ic_wallpaper.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/options_menu_icon_size"
- android:height="@dimen/options_menu_icon_size"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/textColorPrimary">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M9,12.71l2.14,2.58l3-3.87L18,16.57H6L9,12.71z M5,5h6V3H5C3.9,3,3,3.9,3,5v6h2V5z M19,19h-6v2h6c1.1,0,2-0.9,2-2v-6h-2V19z
- M5,19v-6H3v6c0,1.1,0.9,2,2,2h6v-2H5z M19,5v6h2V5c0-1.1-0.9-2-2-2h-6v2H19z M16,9c0.55,0,1-0.45,1-1s-0.45-1-1-1
- c-0.55,0-1,0.45-1,1S15.45,9,16,9z"/>
-</vector>
diff --git a/res/drawable/top_rounded_popup_ripple.xml b/res/drawable/rounded_popup_ripple.xml
similarity index 80%
rename from res/drawable/top_rounded_popup_ripple.xml
rename to res/drawable/rounded_popup_ripple.xml
index 7468480..b0dcc80 100644
--- a/res/drawable/top_rounded_popup_ripple.xml
+++ b/res/drawable/rounded_popup_ripple.xml
@@ -18,10 +18,7 @@
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="#FFFFFFFF"/>
- <corners android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"
- android:topLeftRadius="@dimen/dialogCornerRadius"
- android:topRightRadius="@dimen/dialogCornerRadius"/>
+ <corners android:radius="@dimen/dialogCornerRadius" />
</shape>
</item>
</ripple>
\ No newline at end of file
diff --git a/res/interpolator/back_cancel.xml b/res/interpolator/back_cancel.xml
deleted file mode 100644
index 2165457..0000000
--- a/res/interpolator/back_cancel.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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.
-*/
--->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.2"
- android:controlY1="0"
- android:controlX2="0"
- android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/interpolator/fast_out_extra_slow_in.xml b/res/interpolator/fast_out_extra_slow_in.xml
deleted file mode 100644
index f296a82..0000000
--- a/res/interpolator/fast_out_extra_slow_in.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1"/>
\ No newline at end of file
diff --git a/res/interpolator/folder_interpolator.xml b/res/interpolator/folder_interpolator.xml
deleted file mode 100644
index b95d454..0000000
--- a/res/interpolator/folder_interpolator.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.2"
- android:controlY1="0"
- android:controlX2="0"
- android:controlY2="1"/>
diff --git a/res/interpolator/large_folder_preview_item_close_interpolator.xml b/res/interpolator/large_folder_preview_item_close_interpolator.xml
deleted file mode 100644
index d28af63..0000000
--- a/res/interpolator/large_folder_preview_item_close_interpolator.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2017, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.3"
- android:controlY1="0"
- android:controlX2="1"
- android:controlY2="1"/>
diff --git a/res/interpolator/standard_accelerate.xml b/res/interpolator/standard_accelerate.xml
deleted file mode 100644
index 394393d..0000000
--- a/res/interpolator/standard_accelerate.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:controlX1="0.3"
- android:controlY1="0"
- android:controlX2="1"
- android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/layout/all_apps_prediction_row_icon.xml b/res/layout/all_apps_prediction_row_icon.xml
new file mode 100644
index 0000000..c9b3275
--- /dev/null
+++ b/res/layout/all_apps_prediction_row_icon.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ style="@style/BaseIcon.AllApps"
+ android:id="@+id/icon"
+ launcher:iconDisplay="prediction_row"
+ launcher:centerVertically="true" />
diff --git a/res/layout/app_pair_icon.xml b/res/layout/app_pair_icon.xml
new file mode 100644
index 0000000..2b9a98b
--- /dev/null
+++ b/res/layout/app_pair_icon.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<com.android.launcher3.apppairs.AppPairIcon
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:focusable="true" >
+ <com.android.launcher3.views.DoubleShadowBubbleTextView
+ style="@style/BaseIcon.Workspace"
+ android:id="@+id/app_pair_icon_name"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false"
+ android:layout_gravity="top" />
+</com.android.launcher3.apppairs.AppPairIcon>
diff --git a/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
index 88a92eb..004e778 100644
--- a/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -28,7 +28,7 @@
android:padding="16dp"
android:background="@drawable/arrow_toast_rounded_background"
android:elevation="@dimen/arrow_toast_elevation"
- android:textColor="@color/arrow_tip_view_content"
+ android:textColor="?attr/arrowTipTextColor"
android:textSize="14sp"/>
<View
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
index 4a32672..c41d0bb 100644
--- a/res/layout/widgets_table_container.xml
+++ b/res/layout/widgets_table_container.xml
@@ -16,6 +16,7 @@
<com.android.launcher3.widget.picker.WidgetsListTableView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widgets_table"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
android:background="@drawable/bg_widgets_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 52c5a49..695270e 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -15,7 +15,7 @@
<com.android.launcher3.allapps.WorkPausedCard xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="@dimen/work_edu_card_margin"
+ android:padding="@dimen/all_apps_tabs_margin_top"
android:orientation="vertical"
android:gravity="center_horizontal">
@@ -25,7 +25,6 @@
android:id="@+id/work_apps_paused_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="40dp"
android:text="@string/work_apps_paused_title"
android:textAlignment="center"
android:textSize="18sp" />
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index d9a67c4..aa96397 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Vouer hernoem na <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Vouer: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Vouer: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> of meer items"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Muurpapiere"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Muurpapier en styl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Wysig tuisskerm"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Tuis-instellings"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index dbe2acf..bcc626e 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"አቃፊ <xliff:g id="NAME">%1$s</xliff:g> ተብሎ ዳግም ተሰይሟል"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"አቃፊ፦ <xliff:g id="NAME">%1$s</xliff:g>፣ <xliff:g id="SIZE">%2$d</xliff:g> ንጥሎች"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"አቃፊ፦ <xliff:g id="NAME">%1$s</xliff:g>፣ <xliff:g id="SIZE">%2$d</xliff:g> ወይም ተጨማሪ ንጥሎች"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"የግድግዳ ወረቀቶች"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ልጣፍ እና ቅጥ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"መነሻ ማያ ገጽን አርትዕ"</string>
<string name="settings_button_text" msgid="8873672322605444408">"የመነሻ ቅንብሮች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 90a1e87..69e96cb 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"تمت إعادة تسمية المجلد إلى <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"المجلد: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> عنصر"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"المجلد: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> عنصر أو أكثر"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"الخلفيات"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"الخلفية والأسلوب"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"تعديل الشاشة الرئيسية"</string>
<string name="settings_button_text" msgid="8873672322605444408">"إعدادات الشاشة الرئيسية"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 587b377..8c18e8d 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ফ\'ল্ডাৰৰ নাম সলনি কৰি <xliff:g id="NAME">%1$s</xliff:g> কৰা হৈছে"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ফ’ল্ডাৰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> টা বস্তু"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ফ’ল্ডাৰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> টা অথবা তাতকৈ অধিক বস্তু"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ৱালপেপাৰসমূহ"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ৱালপেপাৰ আৰু শৈলী"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"গৃহ স্ক্ৰীন সম্পাদনা কৰক"</string>
<string name="settings_button_text" msgid="8873672322605444408">"গৃহ ছেটিং"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 1e1b634..bf12761 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Qovluq adı <xliff:g id="NAME">%1$s</xliff:g> ilə dəyişdirildi"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Qovluq: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> element"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Qovluq: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> və ya daha çox element"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Divar kağızları"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Divar kağızı və üslub"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Əsas ekranı redaktə edin"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home ayarları"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 2b3ed53..cfb6fbd 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder je preimenovan u <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> stavke"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ili više stavki"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadine"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Pozadina i stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Izmeni početni ekran"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Podešavanja početnog ekrana"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 1eee242..5950ec5 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Папка перайменавана ў <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, элементы: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, элементы: <xliff:g id="SIZE">%2$d</xliff:g> ці больш"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Шпалеры"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Шпалеры і стыль"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Змяніць Галоўны экран"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Налады галоўнага экрана"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index f59dcfb..b0f1c4c 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Папката е преименувана на „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Папка: „<xliff:g id="NAME">%1$s</xliff:g>“ – <xliff:g id="SIZE">%2$d</xliff:g> елемента"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка: „<xliff:g id="NAME">%1$s</xliff:g>“ – <xliff:g id="SIZE">%2$d</xliff:g> или повече елементи"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тапет и стил"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Редактиране на началния екран"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Настройки за началния екран"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 326fc7b..e28638b 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ফোল্ডারের নাম পাল্টে <xliff:g id="NAME">%1$s</xliff:g> করা হয়েছে"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ফোল্ডার: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g>টি আইটেম"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ফোল্ডার: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g>টি বা তার বেশি আইটেম"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ওয়ালপেপারগুলি"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ওয়ালপেপার এবং স্টাইল"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"হোম স্ক্রিন এডিট করুন"</string>
<string name="settings_button_text" msgid="8873672322605444408">"হোম সেটিংস"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 8c3dd08..8387f14 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Ime foldera je promijenjeno u <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, br. stavki: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ili više stavki"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadinske slike"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Pozadinska slika i stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Uredi Početni ekran"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Postavke početnog ekrana"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 9fe81dd..dd2d03f 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"S\'ha canviat el nom de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elements"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o més elements"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fons de pantalla"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Estil i fons de pantalla"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edita la pantalla d\'inici"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Config. pantalla d\'inici"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 40e1397..f9c7c77 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Složka přejmenována na <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Složka: <xliff:g id="NAME">%1$s</xliff:g>, počet položek: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Složka: <xliff:g id="NAME">%1$s</xliff:g>, počet položek: <xliff:g id="SIZE">%2$d</xliff:g> nebo více"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapeta a styl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Upravit plochu"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Nastavení plochy"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 5867716..134cbb0 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Mappen er omdøbt til <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementer"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eller flere elementer"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Baggrunde"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Baggrund og stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Rediger startskærm"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Indst. for startskærm"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 93ea28e..2fa7f91 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Ordner umbenannt in <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Ordner: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> Elemente"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Ordner: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> oder mehr Elemente"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Hintergründe"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Hintergrund und Stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Startbildschirm bearbeiten"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Einstellungen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 132b20f..35cac5b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Ο φάκελος μετονομάστηκε σε <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Φάκελος: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> στοιχεία"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Φάκελος: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ή περισσότερα στοιχεία"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Ταπετσαρίες"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ταπετσαρία και στιλ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Επεξεργασία αρχικής οθόνης"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Ρυθμίσεις Αρχ. Οθ."</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index aa926df..47a67a8 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edit home screen"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 40534ca..cc86e87 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edit Home Screen"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index aa926df..47a67a8 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edit home screen"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index aa926df..47a67a8 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edit home screen"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index d7a5d95..cca6551 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper & style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edit Home Screen"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index bffa3f8..c49fa0e 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"El nombre de la carpeta se cambió a <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementos"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o más elementos"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fondo de pantalla y estilo"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editar pantalla principal"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Configuración de pantalla principal"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 2ebefc0..b89a717 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Se ha cambiado el nombre de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SIZE">%2$d</xliff:g> elementos)"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SIZE">%2$d</xliff:g> o más elementos)"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fondo de pantalla y estilo"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editar pantalla de inicio"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Ajustes de la pantalla de inicio"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 1859b06..333fded 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Kausta uus nimi: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Kaust: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> üksust"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Kaust: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> või rohkem üksust"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Taustapildid"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Taustapilt ja stiil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Muuda avaekraani"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Avakuva seaded"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 4d617c5..cbf7e1c 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Karpetari <xliff:g id="NAME">%1$s</xliff:g> izena eman zaio"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"<xliff:g id="NAME">%1$s</xliff:g> karpeta (<xliff:g id="SIZE">%2$d</xliff:g> elementu)"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"<xliff:g id="NAME">%1$s</xliff:g> karpeta (<xliff:g id="SIZE">%2$d</xliff:g> elementu edo gehiago)"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Horma-paperak"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Horma-papera eta estiloa"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editatu hasierako pantaila"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Hasierako pantailaren ezarpenak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 091211a..4ec760e 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"نام پوشه به <xliff:g id="NAME">%1$s</xliff:g> تغییر کرد"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> مورد"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> مورد یا بیشتر"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"کاغذدیواریها"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"کاغذدیواری و سبک"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ویرایش «صفحه اصلی»"</string>
<string name="settings_button_text" msgid="8873672322605444408">"تنظیمات صفحه اصلی"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index a1aa48c..16dabb7 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Kansion nimeksi vaihdettiin <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Kansio: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> kohdetta"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Kansio: <xliff:g id="NAME">%1$s</xliff:g>, ainakin <xliff:g id="SIZE">%2$d</xliff:g> kohdetta"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Taustakuvat"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Taustakuva ja tyyli"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Muokkaa aloitusnäyttöä"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Aloitusnäyttö"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 7478968..1267205 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> élément(s)"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments ou plus"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fonds d\'écran"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fond d\'écran et style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Modifier l\'écran d\'accueil"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Paramètres d\'accueil"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 63869f6..1addca7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments ou plus"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fonds d\'écran"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fond d\'écran et style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Modifier l\'écran d\'accueil"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Paramètres de l\'accueil"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index ab68e22..cf6ac1c 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"O cartafol cambiou o nome a <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Cartafol: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementos"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Cartafol: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementos ou máis"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Estilo e fondo de pantalla"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editar pantalla de inicio"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Axustes de Inicio"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 80aaba3..7f5c39f 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ફોલ્ડરનું નામ બદલીને <xliff:g id="NAME">%1$s</xliff:g> કર્યું"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ફોલ્ડર: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> આઇટમ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ફોલ્ડર: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> કે વધુ આઇટમ"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"વૉલપેપર"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"વૉલપેપર અને સ્ટાઇલ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"હોમ સ્ક્રીનમાં ફેરફાર કરો"</string>
<string name="settings_button_text" msgid="8873672322605444408">"હોમ સેટિંગ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 1834b1e..8301194 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"फ़ोल्डर का नाम बदलकर <xliff:g id="NAME">%1$s</xliff:g> किया गया"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"फ़ोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> आइटम"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"फ़ोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> या इससे ज़्यादा आइटम"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"वॉलपेपर"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"वॉलपेपर और स्टाइल"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"होम स्क्रीन में बदलाव करें"</string>
<string name="settings_button_text" msgid="8873672322605444408">"होम स्क्रीन की सेटिंग"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 3e52b18..59630f4 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana u <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> stavke"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ili više stavki"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadine"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Pozadina i stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Uredi početni zaslon"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Postavke početnog zaslona"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 979539d..3bcf350 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"A mappa új neve: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elem"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> vagy több elem"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Háttérképek"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Háttérkép és stílus"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Kezdőképernyő szerkesztése"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Kezdőképernyő beállításai"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 442f4f6..ccc028f 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Պանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Պանակ՝ <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> տարր"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Պանակ՝ <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> կամ ավելի տարրեր"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Պաստառներ"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Պաստառ և ոճ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Փոփոխել հիմնական էկրանը"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Գլխավոր էկրանի կարգավորումներ"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index f16f69f..c1d62d4 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder diganti namanya menjadi <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> item"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> item atau lebih"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpaper"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper & gaya"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edit Layar Utama"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Setelan layar utama"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index fc574cb..e336a4b 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Heiti möppu breytt í <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> atriði"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eða fleiri atriði"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Veggfóður"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Veggfóður og stíll"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Breyta heimaskjá"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Heimastillingar"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 59c0cb7..4342ce6 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nome della cartella sostituito con <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Cartella: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementi"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Cartella: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o più elementi"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Sfondi"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Sfondo e stile"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Modifica la schermata Home"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Impostazioni schermata Home"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 92f5d1d..808d324 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"שם התיקייה שונה ל-<xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"תיקייה: <xliff:g id="NAME">%1$s</xliff:g>, מספר הפריטים: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"תיקייה: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> פריטים או יותר"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"טפטים"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"טפט וסגנון"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"עריכה של מסך הבית"</string>
<string name="settings_button_text" msgid="8873672322605444408">"הגדרות של מסך הבית"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 43cc1e3..a6b1a21 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"フォルダの名前を「<xliff:g id="NAME">%1$s</xliff:g>」に変更しました"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"フォルダ: <xliff:g id="NAME">%1$s</xliff:g>、<xliff:g id="SIZE">%2$d</xliff:g> 件のアイテム"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"フォルダ: <xliff:g id="NAME">%1$s</xliff:g>、<xliff:g id="SIZE">%2$d</xliff:g> 件以上のアイテム"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"壁紙"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁紙とスタイル"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ホーム画面を編集"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ホームの設定"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 7553f6a..7e79faa 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"საქაღალდეს შეეცვალა სახელი „<xliff:g id="NAME">%1$s</xliff:g>“-ად"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"საქაღალდე: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ერთეული"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"საქაღალდე: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ან მეტი ერთეული"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ფონები"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ფონი და სტილი"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"მთავარი ეკრანის რედაქტირება"</string>
<string name="settings_button_text" msgid="8873672322605444408">"მთავარი გვერდის პარამეტრები"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 00babfd..cb514d8 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Қалта атауы <xliff:g id="NAME">%1$s</xliff:g> болып өзгертілді"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Қалта: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> элемент бар"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Қалта: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> не одан көп элемент бар"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Тұсқағаздар"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тұсқағаз және стиль"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Негізгі экранды өзгерту"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Негізгі экран параметрлері"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 42e0d4b..bdb9f0d 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"បានប្ដូរឈ្មោះថតជា <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g> ឬច្រើនជាងនេះ"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ផ្ទាំងរូបភាព"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ផ្ទាំងរូបភាព និងរចនាប័ទ្ម"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"កែអេក្រង់ដើម"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ការកំណត់ទំព័រដើម"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 481c426..a35a70b 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ಫೋಲ್ಡರ್ ಅನ್ನು <xliff:g id="NAME">%1$s</xliff:g> ಗೆ ಮರುಹೆಸರಿಸಲಾಗಿದೆ"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ಫೋಲ್ಡರ್: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ಐಟಂಗಳು"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ಫೋಲ್ಡರ್: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ಅಥವಾ ಹೆಚ್ಚಿನ ಐಟಂಗಳು"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ವಾಲ್ಪೇಪರ್ಗಳು"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ವಾಲ್ಪೇಪರ್ ಮತ್ತು ಶೈಲಿ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ಹೋಮ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ಮುಖಪುಟ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index ab682a4..955af0b 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"폴더 이름 변경: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"폴더: <xliff:g id="NAME">%1$s</xliff:g>, 항목 <xliff:g id="SIZE">%2$d</xliff:g>개"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"폴더: <xliff:g id="NAME">%1$s</xliff:g>, 항목 <xliff:g id="SIZE">%2$d</xliff:g>개 이상"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"배경화면"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"배경화면 및 스타일"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"홈 화면 수정"</string>
<string name="settings_button_text" msgid="8873672322605444408">"홈 설정"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 2047691..7d77440 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Фолдердин аты <xliff:g id="NAME">%1$s</xliff:g> деп өзгөртүлдү"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"<xliff:g id="NAME">%1$s</xliff:g> папкасындагы объекттер: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"<xliff:g id="NAME">%1$s</xliff:g> папкасындагы объекттер: <xliff:g id="SIZE">%2$d</xliff:g> же андан көбүрөөк"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Тушкагаздар"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тушкагаз жана стиль"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Башкы экранды түзөтүү"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Башкы бет параметрлери"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 166c370..9c26cfa 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ປ່ຽນຊື່ໂຟນເດີເປັນ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ໂຟນເດີ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ລາຍການ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ໂຟນເດີ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ຫຼື ລາຍການເພີ່ມເຕີມ"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ພາບພື້ນຫຼັງ"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ຮູບພື້ນຫຼັງ ແລະ ຮູບແບບ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ແກ້ໄຂໂຮມສະກຣີນ"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ການຕັ້ງຄ່າໜ້າຫຼັກ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4c67498..7d69c57 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Aplankas pervardytas kaip „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Aplankas: „<xliff:g id="NAME">%1$s</xliff:g>“, elementų: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Aplankas: „<xliff:g id="NAME">%1$s</xliff:g>“, elementų: <xliff:g id="SIZE">%2$d</xliff:g> ar daugiau"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Ekrano fonai"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ekrano fonas ir stilius"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Redaguoti pagrindinį ekraną"</string>
<string name="settings_button_text" msgid="8873672322605444408">"„Home“ nustatymai"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index ad7af54..6dc9564 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Mape pārdēvēta par: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mape <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> vienumi"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mape <xliff:g id="NAME">%1$s</xliff:g>, vienumu skaits mapē: vismaz <xliff:g id="SIZE">%2$d</xliff:g>"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fona tapetes"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fona tapete un stils"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Rediģēt sākuma ekrānu"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Sākumlapas iestatījumi"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index c2ee822..eff0e15 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Папката е преименувана во <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ставки"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> или повеќе ставки"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тапет и стил"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Изменете го почетниот екран"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Поставки за почетен екран"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 3600edd..d7feb3c 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ഫോൾഡറിന്റെ പേര് <xliff:g id="NAME">%1$s</xliff:g> എന്നായി മാറ്റി"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ഫോൾഡർ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ഇനങ്ങൾ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ഫോൾഡർ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> അല്ലെങ്കിൽ അതിലധികം ഇനങ്ങൾ"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"വാൾപേപ്പർ"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"വാൾപേപ്പറും സ്റ്റൈലും"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ഹോം സ്ക്രീൻ എഡിറ്റ് ചെയ്യുക"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ഹോം ക്രമീകരണം"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 430e80d..716bd85 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Фолдерын нэр <xliff:g id="NAME">%1$s</xliff:g> болов"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> зүйл"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> эсвэл үүнээс олон зүйл"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Дэлгэцийн зураг"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Дэлгэцийн зураг, загвар"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Үндсэн нүүрийг засах"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Нүүр хуудасны тохиргоо"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index b2c023e..c73295d 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"फोल्डरचे नाव बदलून <xliff:g id="NAME">%1$s</xliff:g> असे ठेवले"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> आयटम"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> किंवा त्याहून अधिक आयटम"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"वॉलपेपर"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"वॉलपेपर आणि शैली"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"होम स्क्रीन संपादित करा"</string>
<string name="settings_button_text" msgid="8873672322605444408">"होम सेटिंग्ज"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 5dcc913..e4a2e57 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folder dinamakan semula kepada <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> item"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> atau lebih banyak item"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Hiasan latar"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Hiasan latar & gaya"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edit Skrin Utama"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Tetapan skrin utama"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index e0e98d5..1ec09f5 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ပြောင်းလဲလိုက်သော အကန့်အမည် <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ဖိုင်တွဲ - <xliff:g id="NAME">%1$s</xliff:g>၊ <xliff:g id="SIZE">%2$d</xliff:g> ဖိုင်များ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ဖိုင်တွဲ - <xliff:g id="NAME">%1$s</xliff:g>၊ <xliff:g id="SIZE">%2$d</xliff:g> သို့မဟုတ် နောက်ထပ်ဖိုင်များ"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"နောက်ခံများ"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"နောက်ခံနှင့် ပုံစံ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ပင်မစာမျက်နှာ တည်းဖြတ်ရန်"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ပင်မဆက်တင်များ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 485f845..7266a81 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Mappen heter nå <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementer"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eller flere elementer"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Bakgrunner"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Bakgrunn og stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Endre startsiden"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Startsideinnstillinger"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 4219dce..a73f2a3 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"फोल्डर <xliff:g id="NAME">%1$s</xliff:g> मा पुनःनामाकरण गरियो।"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> वस्तुहरू"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> वा सोभन्दा बढी वस्तुहरू"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"वालपेपरहरु"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"वालपेपर तथा शैली"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"होम स्क्रिन बदल्नुहोस्"</string>
<string name="settings_button_text" msgid="8873672322605444408">"होम पेजका सेटिङहरू"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 416d82d..8d2aa21 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"De naam van de map is gewijzigd in <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Map: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Map: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> of meer items"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Achtergrond"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Achtergrond en stijl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Startscherm bewerken"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Instellingen start"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index c3345a0..555913e 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ଫୋଲ୍ଡରର ନାମ <xliff:g id="NAME">%1$s</xliff:g>କୁ ବଦଳାଗଲା"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ଫୋଲ୍ଡର୍: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ଆଇଟମଗୁଡ଼ିକ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ଫୋଲ୍ଡର୍: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> କିମ୍ବା ଅଧିକ ଆଇଟମ୍"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ୱାଲପେପର୍"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ୱାଲପେପର ଏବଂ ଷ୍ଟାଇଲ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ହୋମ ସ୍କ୍ରିନକୁ ଏଡିଟ କରନ୍ତୁ"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ହୋମ ସେଟିଂସ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index f9a66df..e6e3f07 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ਫੋਲਡਰ ਨੂੰ <xliff:g id="NAME">%1$s</xliff:g> ਮੁੜ ਨਾਮ ਦਿੱਤਾ ਗਿਆ"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ਫੋਲਡਰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ਆਈਟਮਾਂ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ਫੋਲਡਰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ਜਾਂ ਹੋਰ ਆਈਟਮਾਂ"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"ਵਾਲਪੇਪਰ"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ਵਾਲਪੇਪਰ ਅਤੇ ਸਟਾਈਲ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ਹੋਮ ਸਕ੍ਰੀਨ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ਹੋਮ ਸੈਟਿੰਗਾਂ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 4f3abe3..739ef6c 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nazwa folderu zmieniona na <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementy"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, liczba elementów: <xliff:g id="SIZE">%2$d</xliff:g> lub więcej"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapeta i styl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Edytuj ekran główny"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Ustawienia ekranu głównego"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 0acf63e..76c65a4 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nome de pasta alterado para <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> itens"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ou mais itens"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagens de fundo"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Imagem fundo/estilo"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editar ecrã principal"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Definições de início"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index e0c9b84..e23f459 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Pasta renomeada para <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> itens"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ou mais itens"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Planos de fundo"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Plano de fundo e estilo"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editar tela inicial"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Configurações da tela inicial"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index d6c5e5e..fc9c5d1 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Dosar redenumit <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Dosar: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elemente"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Dosar: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> sau mai multe elemente"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagini de fundal"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Imagine de fundal și stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editează ecranul de pornire"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Setări ecran de pornire"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 6315ee5..b3ff865 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Папка переименована в \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\" (объектов: <xliff:g id="SIZE">%2$d</xliff:g>)"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\" (объектов: <xliff:g id="SIZE">%2$d</xliff:g> или больше)"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Обои"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Обои и стиль"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Изменить главный экран"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Главный экран"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 15bc4a7..6f93989 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"<xliff:g id="NAME">%1$s</xliff:g> වෙත ෆෝල්ඩරය නැවත නම් කෙරිණි"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ෆෝල්ඩරය: <xliff:g id="NAME">%1$s</xliff:g>, අයිතම <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ෆෝල්ඩර: <xliff:g id="NAME">%1$s</xliff:g>, අයිතම <xliff:g id="SIZE">%2$d</xliff:g>ක් හෝ වැඩි ගණනක්"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"වෝල්පේපර"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"වෝල්පේපරය සහ මෝස්තරය"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"මුල් තිරය සංස්කරණය කරන්න"</string>
<string name="settings_button_text" msgid="8873672322605444408">"නිවසේ සැකසීම්"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 2028c35..742d726 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Priečinok bol premenovaný na <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Priečinok: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> položky"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Priečinok: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> alebo viac položiek"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapeta a štýl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Upraviť plochu"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Nastavenia plochy"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index e6197c7..57289d3 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana v <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, št. elementov: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ali več elementov"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Ozadja"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Zaslonsko ozadje in slog"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Urejanje začetnega zaslona"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Domače nastavitve"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index c6b8e7c..7dee8aa 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Dosja u riemërtua në <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Dosja: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> artikuj"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Dosja: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ose më shumë artikuj"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Imazhet e sfondit"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Imazhi i sfondit dhe stili"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Modifiko ekranin bazë"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Cilësimet e ekranit bazë"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index a4d7795..7121c53 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Фолдер је преименован у <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ставке"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> или више ставки"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Позадине"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Позадина и стил"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Измени почетни екран"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Подешавања почетног екрана"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index cd946e7..aeaf708 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Mappen har bytt namn till <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> objekt"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eller fler objekt"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Bakgrunder"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Bakgrund och utseende"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Redigera startskärm"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Startinställningar"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 2686f8a..5be3bf3 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Folda imebadilishwa jina kuwa <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folda: <xliff:g id="NAME">%1$s</xliff:g>, vipengee <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folda: <xliff:g id="NAME">%1$s</xliff:g>, vipengee <xliff:g id="SIZE">%2$d</xliff:g> au zaidi"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Mandhari"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Mandhari na mtindo"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Badilisha Skrini ya Kwanza"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Mipangilio ya mwanzo"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 8d9b2cc..ec287f5 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ஃபோல்டர் <xliff:g id="NAME">%1$s</xliff:g> என மறுபெயரிடப்பட்டது"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ஃபோல்டர்: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ஃபைல்கள்"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ஃபோல்டர்: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> அல்லது அதற்கு அதிகமான ஃபைல்கள்"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"வால்பேப்பர்கள்"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"வால்பேப்பர் & ஸ்டைல்"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"முகப்புத் திரையில் மாற்று"</string>
<string name="settings_button_text" msgid="8873672322605444408">"முகப்பு அமைப்புகள்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 08e633d..032a241 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"ఫోల్డర్ పేరు <xliff:g id="NAME">%1$s</xliff:g>గా మార్చబడింది"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"ఫోల్డర్: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ఐటెమ్లు"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ఫోల్డర్: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> లేదా అంతకంటే ఎక్కువ ఐటెమ్లు"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"వాల్పేపర్లు"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"వాల్పేపర్ & స్టయిల్"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"మొదటి స్క్రీన్ను ఎడిట్ చేయండి"</string>
<string name="settings_button_text" msgid="8873672322605444408">"మొదటి స్క్రీన్ సెట్టింగ్లు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 4b64993..fe79b12 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"เปลี่ยนชื่อโฟลเดอร์เป็น <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, อย่างน้อย <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"วอลเปเปอร์"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"วอลเปเปอร์และรูปแบบ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"แก้ไขหน้าจอหลัก"</string>
<string name="settings_button_text" msgid="8873672322605444408">"การตั้งค่าหน้าจอหลัก"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 61f9227..fe4aac1 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Pinalitan ang pangalan ng folder ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> (na) item"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o higit pang item"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Mga Wallpaper"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper & istilo"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"I-edit ang Home Screen"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Mga setting ng Home"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index ef28f18..8a836e6 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Klasörün adı <xliff:g id="NAME">%1$s</xliff:g> olarak değiştirildi"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> öğe"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> veya daha fazla öğe"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Duvar Kağıtları"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Duvar kağıdı ve stil"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Ana ekranı düzenleyin"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Ana ekran ayarları"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 822936b..5dc5d8e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Папку перейменовано на <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\", елементів: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\", елементів: <xliff:g id="SIZE">%2$d</xliff:g> або більше"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Фонові малюнки"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Оформлення і стиль"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Редагувати головний екран"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Налаштування головного екрана"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 1fffcfd..3994810 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"فولڈر کا نام تبدیل کر کے <xliff:g id="NAME">%1$s</xliff:g> کر دیا گیا"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"فولڈر: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> آئٹمز"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"فولڈر: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> یا مزید آئٹمز"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"وال پیپرز"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"وال پیپر اور طرز"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ہوم اسکرین میں ترمیم کریں"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ہوم ترتیبات"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index db381ca..405c4ef 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Jild nomi <xliff:g id="NAME">%1$s</xliff:g>ga o‘zgartirildi"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Jild: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> fayllar"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Jild: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> va undan ortiq fayllar"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Fon rasmlari"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fon rasmi va uslubi"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Bosh ekranni tahrirlash"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Bosh ekran sozlamalari"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b73bc61..075a7b0 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Đã đổi tên thư mục thành <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Thư mục: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> mục"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Thư mục: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> mục trở lên"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Hình nền"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Hình nền và phong cách"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Chỉnh sửa Màn hình chính"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Cài đặt màn hình chính"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 20c8ea9..e525c02 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"已将文件夹重命名为“<xliff:g id="NAME">%1$s</xliff:g>”"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 个项目"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 个或更多项目"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"壁纸"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁纸与个性化"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"修改主屏幕"</string>
<string name="settings_button_text" msgid="8873672322605444408">"主屏幕设置"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 75f191e..0f0960e 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"資料夾已重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個項目"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個或以上的項目"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"桌布和樣式"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"編輯主畫面"</string>
<string name="settings_button_text" msgid="8873672322605444408">"主畫面設定"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 69d1d90..cf40155 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"已將資料夾重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個項目"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個以上的項目"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"桌布和樣式"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"編輯主畫面"</string>
<string name="settings_button_text" msgid="8873672322605444408">"主畫面設定"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index d9ed3f0..6ccc4be 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -99,7 +99,6 @@
<string name="folder_renamed" msgid="1794088362165669656">"Ifolda iqanjwe kabusha ngo-<xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Ifolda: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> izinto"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Ifolda: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> noma izinto eziningi"</string>
- <string name="wallpaper_button_text" msgid="8404103075899945851">"Izithombe zangemuva"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Isithombe sangemuva nesitayela"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Hlela Isikrini Sasekhaya"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Amasethingi asekhaya"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e4650b2..f046eca 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -36,7 +36,6 @@
<attr name="workspaceShadowColor" format="color" />
<attr name="workspaceAmbientShadowColor" format="color" />
<attr name="workspaceKeyShadowColor" format="color" />
- <attr name="workspaceStatusBarScrim" format="reference" />
<attr name="widgetsTheme" format="reference" />
<attr name="iconOnlyShortcutColor" format="color" />
<attr name="eduHalfSheetBGColor" format="color" />
@@ -86,6 +85,8 @@
<enum name="taskbar" value="5" />
<enum name="search_result_tall" value="6" />
<enum name="search_result_small" value="7" />
+ <enum name="prediction_row" value="8" />
+ <enum name="search_result_app_row" value="9" />
</attr>
<attr name="centerVertically" format="boolean" />
</declare-styleable>
@@ -202,9 +203,24 @@
<attr name="demoModeLayoutId" format="reference" />
<attr name="isScalable" format="boolean" />
<attr name="devicePaddingId" format="reference" />
+
<!-- File that contains the specs for the workspace.
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="workspaceSpecsId" format="reference" />
+ <attr name="workspaceSpecsTwoPanelId" format="reference" />
+ <!-- File that contains the specs for all apps.
+ Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+ <attr name="allAppsSpecsId" format="reference" />
+ <attr name="allAppsSpecsTwoPanelId" format="reference" />
+ <!-- File that contains the specs for the workspace.
+ Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+ <attr name="folderSpecsId" format="reference" />
+ <attr name="folderSpecsTwoPanelId" format="reference" />
+ <!-- File that contains the specs for hotseat bar.
+ Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+ <attr name="hotseatSpecsId" format="reference" />
+ <attr name="hotseatSpecsTwoPanelId" format="reference" />
+
<!-- By default all categories are enabled -->
<attr name="deviceCategory" format="integer">
<!-- Enable on phone only -->
@@ -244,7 +260,7 @@
</declare-styleable>
<!-- Responsive grids attributes -->
- <declare-styleable name="WorkspaceSpec">
+ <declare-styleable name="ResponsiveSpec">
<attr name="specType" format="integer">
<enum name="height" value="0" />
<enum name="width" value="1" />
@@ -252,10 +268,32 @@
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
- <declare-styleable name="SpecSize">
+ <declare-styleable name="WorkspaceSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
+ <declare-styleable name="FolderSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
+ <declare-styleable name="AllAppsSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
+ <declare-styleable name="HotseatSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
+ <declare-styleable name="SizeSpec">
<attr name="fixedSize" format="dimension" />
<attr name="ofAvailableSpace" format="float" />
<attr name="ofRemainderSpace" format="float" />
+ <attr name="matchWorkspace" format="boolean" />
+ <attr name="maxSize" format="dimension" />
</declare-styleable>
<declare-styleable name="ProfileDisplayOption">
@@ -564,4 +602,9 @@
<!-- The icon drawable of a widget category. -->
<attr name="sectionDrawable" format="reference" />
</declare-styleable>
+
+ <declare-styleable name="ArrowTipView">
+ <attr name="arrowTipBackground" format="color" />
+ <attr name="arrowTipTextColor" format="color" />
+ </declare-styleable>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 5a6698b..8f9731c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -84,6 +84,8 @@
<string name="window_manager_proxy_class" translatable="false"></string>
<string name="secondary_display_predictions_class" translatable="false"></string>
<string name="widget_holder_factory_class" translatable="false"></string>
+ <string name="taskbar_search_session_controller_class" translatable="false"></string>
+ <string name="taskbar_model_callbacks_factory_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />
@@ -102,8 +104,6 @@
<!-- Default packages -->
<string name="wallpaper_picker_package" translatable="false"></string>
- <string name="custom_activity_picker" translatable="false">
- com.android.customization.picker.CustomizationPickerActivity</string>
<string name="local_colors_extraction_class" translatable="false"></string>
<string name="search_session_manager_class" translatable="false"></string>
@@ -217,4 +217,28 @@
<!-- Whether the floating rotation button should be on the left/right in the device's natural
orientation -->
<bool name="floating_rotation_button_position_left">true</bool>
+
+ <!-- Mapping of visual icon size to XML value http://b/235886078 -->
+ <dimen name="iconSize48dp">52dp</dimen>
+ <dimen name="iconSize50dp">55dp</dimen>
+ <dimen name="iconSize52dp">57dp</dimen>
+ <dimen name="iconSize54dp">59dp</dimen>
+ <dimen name="iconSize56dp">61dp</dimen>
+ <dimen name="iconSize58dp">63dp</dimen>
+ <dimen name="iconSize60dp">66dp</dimen>
+ <dimen name="iconSize66dp">72dp</dimen>
+ <dimen name="iconSize72dp">79dp</dimen>
+
+ <!-- Icon size steps in dp -->
+ <integer-array name="icon_size_steps">
+ <item>@dimen/iconSize48dp</item>
+ <item>@dimen/iconSize50dp</item>
+ <item>@dimen/iconSize52dp</item>
+ <item>@dimen/iconSize54dp</item>
+ <item>@dimen/iconSize56dp</item>
+ <item>@dimen/iconSize58dp</item>
+ <item>@dimen/iconSize60dp</item>
+ <item>@dimen/iconSize66dp</item>
+ <item>@dimen/iconSize72dp</item>
+ </integer-array>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 786088e..1079e00 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -411,6 +411,7 @@
<dimen name="split_instructions_elevation">1dp</dimen>
<dimen name="split_instructions_horizontal_padding">24dp</dimen>
<dimen name="split_instructions_vertical_padding">12dp</dimen>
+ <dimen name="split_instructions_drawable_padding">10dp</dimen>
<dimen name="split_instructions_bottom_margin_phone_landscape">24dp</dimen>
<dimen name="split_instructions_bottom_margin_phone_portrait">60dp</dimen>
@@ -438,4 +439,8 @@
<!-- Folder spaces -->
<dimen name="folder_top_padding_default">24dp</dimen>
<dimen name="folder_footer_horiz_padding">20dp</dimen>
+
+ <!-- Default Ime height. Used only for logging purposes.
+ Assume this is default keyboard height in EN locale in case the keyboard height is not known when queried.-->
+ <dimen name="default_ime_height">300dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c2eb373..1b46b4d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -250,8 +250,6 @@
<!-- Strings for the customization mode -->
<!-- Text for wallpaper change button [CHAR LIMIT=30]-->
- <string name="wallpaper_button_text">Wallpapers</string>
- <!-- Text for wallpaper change button [CHAR LIMIT=30]-->
<string name="styles_wallpaper_button_text">Wallpaper & style</string>
<!-- Text for edit home screen button [CHAR LIMIT=30]-->
<string name="edit_home_screen">Edit Home Screen</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index c41f0e8..4cb6414 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -32,7 +32,8 @@
<style name="LauncherTheme" parent="@style/BaseLauncherTheme">
<item name="android:textColorSecondary">#DE000000</item>
<item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
- <item name="allappsHeaderProtectionColor">@color/popup_color_tertiary_light</item>
+ <item name="allappsHeaderProtectionColor">
+ @color/material_color_surface_container_highest</item>
<item name="allAppsNavBarScrimColor">#66FFFFFF</item>
<item name="popupColorPrimary">@color/popup_color_primary_light</item>
<item name="popupColorSecondary">@color/popup_color_secondary_light</item>
@@ -50,7 +51,6 @@
<item name="workspaceShadowColor">#B0000000</item>
<item name="workspaceAmbientShadowColor">#40000000</item>
<item name="workspaceKeyShadowColor">#89000000</item>
- <item name="workspaceStatusBarScrim">@drawable/workspace_bg</item>
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="folderPaginationColor">@color/folder_pagination_color_light</item>
<item name="folderPreviewColor">@color/folder_preview_light</item>
@@ -137,7 +137,6 @@
<item name="workspaceAmbientShadowColor">@android:color/transparent</item>
<item name="workspaceKeyShadowColor">@android:color/transparent</item>
<item name="isWorkspaceDarkText">true</item>
- <item name="workspaceStatusBarScrim">@null</item>
<item name="workspaceAccentColor">@color/workspace_accent_color_dark</item>
<item name="dropTargetHoverTextColor">@color/drop_target_hover_text_color_dark</item>
<item name="dropTargetHoverButtonColor">@color/drop_target_hover_button_color_dark</item>
@@ -151,6 +150,7 @@
<item name="android:colorControlHighlight">#19FFFFFF</item>
<item name="android:colorPrimary">#FF212121</item>
<item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
+ <item name="allappsHeaderProtectionColor">@color/material_color_surface_container_low</item>
<item name="allAppsNavBarScrimColor">#80000000</item>
<item name="popupColorPrimary">@color/popup_color_primary_dark</item>
<item name="popupColorSecondary">@color/popup_color_secondary_dark</item>
@@ -188,7 +188,6 @@
<item name="workspaceAmbientShadowColor">@android:color/transparent</item>
<item name="workspaceKeyShadowColor">@android:color/transparent</item>
<item name="isWorkspaceDarkText">true</item>
- <item name="workspaceStatusBarScrim">@null</item>
<item name="workspaceAccentColor">@color/workspace_accent_color_dark</item>
<item name="dropTargetHoverTextColor">@color/drop_target_hover_text_color_dark</item>
<item name="dropTargetHoverButtonColor">@color/drop_target_hover_button_color_dark</item>
@@ -426,4 +425,8 @@
<item name="horizontalPadding">16dp</item>
</style>
+ <style name="ArrowTipStyle">
+ <item name="arrowTipBackground">@color/arrow_tip_view_bg</item>
+ <item name="arrowTipTextColor">@color/arrow_tip_view_content</item>
+ </style>
</resources>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 31f9bfe..b845c88 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -133,6 +133,8 @@
public static final int TYPE_TASKBAR_OVERLAYS =
TYPE_TASKBAR_ALL_APPS | TYPE_TASKBAR_EDUCATION_DIALOG;
+ public static final int TYPE_ALL_EXCEPT_ON_BOARD_POPUP = TYPE_ALL & ~TYPE_ON_BOARD_POPUP;
+
protected boolean mIsOpen;
public AbstractFloatingView(Context context, AttributeSet attrs) {
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index ede7e2f..c7cdfa8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -17,6 +17,8 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -30,7 +32,6 @@
import android.content.res.Resources.NotFoundException;
import android.content.res.XmlResourceParser;
import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.text.TextUtils;
@@ -44,7 +45,6 @@
import androidx.annotation.WorkerThread;
import androidx.annotation.XmlRes;
-import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -619,9 +619,7 @@
// failed to add, and less than 2 were actually added
if (folderItems.size() < 2) {
// Delete the folder
- Uri uri = Favorites.getContentUri(folderId);
- SqlArguments args = new SqlArguments(uri, null, null);
- mDb.delete(args.table, args.where, args.args);
+ mDb.delete(TABLE_NAME, itemIdMatch(folderId), null);
addedId = -1;
// If we have a single item, promote it to where the folder
@@ -634,7 +632,7 @@
copyInteger(myValues, childValues, Favorites.CELLY);
addedId = folderItems.get(0);
- mDb.update(Favorites.TABLE_NAME, childValues,
+ mDb.update(TABLE_NAME, childValues,
Favorites._ID + "=" + addedId, null);
}
}
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 8876a1b..808cf70 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -17,12 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
-import android.app.WallpaperManager.OnColorsChangedListener;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
@@ -32,6 +27,7 @@
import android.view.Display;
import android.view.View;
+import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -41,9 +37,11 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.OnColorHintListener;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.WallpaperColorHints;
import com.android.launcher3.util.WindowBounds;
/**
@@ -51,7 +49,7 @@
*/
@SuppressWarnings("NewApi")
public abstract class BaseDraggingActivity extends BaseActivity
- implements OnColorsChangedListener, DisplayInfoChangeListener {
+ implements OnColorHintListener, DisplayInfoChangeListener {
private static final String TAG = "BaseDraggingActivity";
@@ -63,8 +61,7 @@
protected boolean mIsSafeModeEnabled;
private Runnable mOnStartCallback;
- private RunnableList mOnResumeCallbacks = new RunnableList();
-
+ private final RunnableList mOnResumeCallbacks = new RunnableList();
private int mThemeRes = R.style.AppTheme;
@Override
@@ -76,10 +73,7 @@
DisplayController.INSTANCE.get(this).addChangeListener(this);
// Update theme
- if (Utilities.ATLEAST_P) {
- THREAD_POOL_EXECUTOR.execute(() -> getSystemService(WallpaperManager.class)
- .addOnColorsChangedListener(this, MAIN_EXECUTOR.getHandler()));
- }
+ WallpaperColorHints.get(this).registerOnColorHintsChangedListener(this);
int themeRes = Themes.getActivityThemeRes(this);
if (themeRes != mThemeRes) {
mThemeRes = themeRes;
@@ -97,8 +91,9 @@
mOnResumeCallbacks.add(callback);
}
+ @MainThread
@Override
- public void onColorsChanged(WallpaperColors wallpaperColors, int which) {
+ public void onColorHintsChanged(int colorHints) {
updateTheme();
}
@@ -175,10 +170,8 @@
@Override
protected void onDestroy() {
super.onDestroy();
- if (Utilities.ATLEAST_P) {
- getSystemService(WallpaperManager.class).removeOnColorsChangedListener(this);
- }
DisplayController.INSTANCE.get(this).removeChangeListener(this);
+ WallpaperColorHints.get(this).unregisterOnColorsChangedListener(this);
}
public void runOnceOnStart(Runnable action) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index f920d75..a539148 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
@@ -94,11 +95,13 @@
IconLabelDotView, DraggableView, Reorderable {
private static final int DISPLAY_WORKSPACE = 0;
- private static final int DISPLAY_ALL_APPS = 1;
+ public static final int DISPLAY_ALL_APPS = 1;
private static final int DISPLAY_FOLDER = 2;
protected static final int DISPLAY_TASKBAR = 5;
- private static final int DISPLAY_SEARCH_RESULT = 6;
- private static final int DISPLAY_SEARCH_RESULT_SMALL = 7;
+ public static final int DISPLAY_SEARCH_RESULT = 6;
+ public static final int DISPLAY_SEARCH_RESULT_SMALL = 7;
+ public static final int DISPLAY_PREDICTION_ROW = 8;
+ public static final int DISPLAY_SEARCH_RESULT_APP_ROW = 9;
private static final float MIN_LETTER_SPACING = -0.05f;
private static final int MAX_SEARCH_LOOP_COUNT = 20;
@@ -151,7 +154,7 @@
private final CheckLongPressHelper mLongPressHelper;
- private final boolean mLayoutHorizontal;
+ private boolean mLayoutHorizontal;
private final boolean mIsRtl;
private final int mIconSize;
@@ -196,6 +199,7 @@
public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mActivity = ActivityContext.lookupContext(context);
+ FastBitmapDrawable.setFlagHoverEnabled(ENABLE_CURSOR_HOVER_STATES.get());
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
@@ -210,8 +214,9 @@
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
defaultIconSize = grid.iconSizePx;
- setCenterVertically(grid.isScalableGrid);
- } else if (mDisplay == DISPLAY_ALL_APPS) {
+ setCenterVertically(grid.iconCenterVertically);
+ } else if (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW
+ || mDisplay == DISPLAY_SEARCH_RESULT_APP_ROW) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
defaultIconSize = grid.allAppsIconSizePx;
@@ -402,7 +407,7 @@
* Only if actual text can be displayed in two line, the {@code true} value will be effective.
*/
protected boolean shouldUseTwoLine() {
- return (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && mDisplay == DISPLAY_ALL_APPS)
+ return (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && mDisplay == DISPLAY_ALL_APPS)
|| (FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()
&& mDisplay == DISPLAY_SEARCH_RESULT);
}
@@ -424,10 +429,10 @@
}
}
- /** This is used for testing to forcefully set the display to ALL_APPS */
+ /** This is used for testing to forcefully set the display. */
@VisibleForTesting
- public void setDisplayAllApps() {
- mDisplay = DISPLAY_ALL_APPS;
+ public void setDisplay(int display) {
+ mDisplay = display;
}
/**
@@ -631,6 +636,11 @@
}
}
+ @VisibleForTesting
+ public boolean getForceHideDot() {
+ return mForceHideDot;
+ }
+
private boolean hasDot() {
return mDotInfo != null;
}
@@ -660,6 +670,18 @@
}
/**
+ * Sets whether the layout is horizontal.
+ */
+ public void setLayoutHorizontal(boolean layoutHorizontal) {
+ if (mLayoutHorizontal == layoutHorizontal) {
+ return;
+ }
+
+ mLayoutHorizontal = layoutHorizontal;
+ applyCompoundDrawables(getIconOrTransparentColor());
+ }
+
+ /**
* Sets whether to vertically center the content.
*/
public void setCenterVertically(boolean centerVertically) {
@@ -985,10 +1007,14 @@
if (!mIsIconVisible) {
resetIconScale();
}
- Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
+ Drawable icon = getIconOrTransparentColor();
applyCompoundDrawables(icon);
}
+ private Drawable getIconOrTransparentColor() {
+ return mIsIconVisible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
+ }
+
/** Sets the icon visual state to disabled or not. */
public void setIconDisabled(boolean isDisabled) {
if (mIcon != null) {
@@ -1080,8 +1106,13 @@
}
public boolean isDisplaySearchResult() {
- return mDisplay == DISPLAY_SEARCH_RESULT ||
- mDisplay == DISPLAY_SEARCH_RESULT_SMALL;
+ return mDisplay == DISPLAY_SEARCH_RESULT
+ || mDisplay == DISPLAY_SEARCH_RESULT_SMALL
+ || mDisplay == DISPLAY_SEARCH_RESULT_APP_ROW;
+ }
+
+ public int getIconDisplay() {
+ return mDisplay;
}
@Override
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 108e557..3d715e5 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -35,7 +35,7 @@
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.anim.Interpolators;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -258,7 +258,7 @@
dragLayer.animateView(d.dragView, to, scale, 0.1f, 0.1f,
DRAG_VIEW_DROP_DURATION,
- Interpolators.DEACCEL_2, onAnimationEndRunnable,
+ Interpolators.DECELERATE_2, onAnimationEndRunnable,
DragLayer.ANIMATION_END_DISAPPEAR, null);
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 61b2748..08e5def 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -18,8 +18,8 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
+import static com.android.app.animation.Interpolators.DECELERATE_1_5;
import static com.android.launcher3.LauncherState.EDIT_MODE;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_BOUNCE_OFFSET;
@@ -60,9 +60,9 @@
import androidx.core.graphics.ColorUtils;
import androidx.core.view.ViewCompat;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.celllayout.ReorderAlgorithm;
@@ -245,6 +245,7 @@
// the user where a dragged item will land when dropped.
setWillNotDraw(false);
setClipToPadding(false);
+ setClipChildren(false);
mActivity = ActivityContext.lookupContext(context);
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
@@ -272,7 +273,7 @@
mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
// Initialize the data structures used for the drag visualization.
- mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
+ mEaseOutInterpolator = Interpolators.DECELERATE_QUINT; // Quint ease out
mDragCell[0] = mDragCell[1] = -1;
mDragCellSpan[0] = mDragCellSpan[1] = -1;
for (int i = 0; i < mDragOutlines.length; i++) {
@@ -382,8 +383,7 @@
private void resetCellSizeInternal(DeviceProfile deviceProfile) {
switch (mContainerType) {
case FOLDER:
- mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx,
- deviceProfile.folderCellLayoutBorderSpacePx);
+ mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx);
break;
case HOTSEAT:
mBorderSpace = new Point(deviceProfile.hotseatBorderSpace,
@@ -1606,7 +1606,7 @@
ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
animationProgress, 0);
a = va;
- a.setInterpolator(DEACCEL_1_5);
+ a.setInterpolator(DECELERATE_1_5);
a.setDuration(REORDER_ANIMATION_DURATION);
a.start();
}
@@ -2736,11 +2736,13 @@
}
public boolean isOccupied(int x, int y) {
- if (x < mCountX && y < mCountY) {
+ if (x >= 0 && x < mCountX && y >= 0 && y < mCountY) {
return mOccupied.cells[x][y];
- } else {
+ }
+ if (BuildConfig.IS_STUDIO_BUILD) {
throw new RuntimeException("Position exceeds the bound of this CellLayout");
}
+ return true;
}
@Override
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index fa1cbd6..936229a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,13 +16,13 @@
package com.android.launcher3;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.Utilities.pxFromSp;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
@@ -53,13 +53,20 @@
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.responsive.AllAppsSpecs;
+import com.android.launcher3.responsive.CalculatedAllAppsSpec;
+import com.android.launcher3.responsive.CalculatedFolderSpec;
+import com.android.launcher3.responsive.CalculatedHotseatSpec;
+import com.android.launcher3.responsive.CalculatedWorkspaceSpec;
+import com.android.launcher3.responsive.FolderSpecs;
+import com.android.launcher3.responsive.HotseatSpecs;
+import com.android.launcher3.responsive.WorkspaceSpecs;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.IconSizeSteps;
import com.android.launcher3.util.ResourceHelper;
import com.android.launcher3.util.WindowBounds;
-import com.android.launcher3.workspace.CalculatedWorkspaceSpec;
-import com.android.launcher3.workspace.WorkspaceSpecs;
import java.io.PrintWriter;
import java.util.Locale;
@@ -80,6 +87,7 @@
public final InvariantDeviceProfile inv;
private final Info mInfo;
private final DisplayMetrics mMetrics;
+ private final IconSizeSteps mIconSizeSteps;
// Device properties
public final boolean isTablet;
@@ -104,14 +112,18 @@
public final float aspectRatio;
- public final boolean isScalableGrid;
+ private final boolean mIsScalableGrid;
private final int mTypeIndex;
// Responsive grid
private final boolean mIsResponsiveGrid;
- private WorkspaceSpecs mWorkspaceSpecs;
private CalculatedWorkspaceSpec mResponsiveWidthSpec;
private CalculatedWorkspaceSpec mResponsiveHeightSpec;
+ private CalculatedAllAppsSpec mAllAppsResponsiveWidthSpec;
+ private CalculatedAllAppsSpec mAllAppsResponsiveHeightSpec;
+ private CalculatedFolderSpec mResponsiveFolderWidthSpec;
+ private CalculatedFolderSpec mResponsiveFolderHeightSpec;
+ private CalculatedHotseatSpec mResponsiveHotseatSpec;
/**
* The maximum amount of left/right workspace padding as a percentage of the screen width.
@@ -152,14 +164,15 @@
public int iconSizePx;
public int iconTextSizePx;
public int iconDrawablePaddingPx;
- public int iconDrawablePaddingOriginalPx;
+ private final int mIconDrawablePaddingOriginalPx;
+ public boolean iconCenterVertically;
public float cellScaleToFit;
public int cellWidthPx;
public int cellHeightPx;
public int workspaceCellPaddingXPx;
- public int cellYPaddingPx;
+ public int cellYPaddingPx = -1;
// Folder
public float folderLabelTextScale;
@@ -169,7 +182,7 @@
public int folderIconOffsetYPx;
// Folder content
- public int folderCellLayoutBorderSpacePx;
+ public Point folderCellLayoutBorderSpacePx;
public int folderContentPaddingLeftRight;
public int folderContentPaddingTop;
@@ -281,6 +294,7 @@
public final int taskbarIconSize;
// If true, used to layout taskbar in 3 button navigation mode.
public final boolean startAlignTaskbar;
+ public final boolean isTransientTaskbar;
// DragController
public int flingToDeleteThresholdVelocity;
@@ -304,9 +318,12 @@
mInsets.set(windowBounds.insets);
// TODO(b/241386436): shouldn't change any launcher behaviour
- mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
+ mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE
+ && inv.allAppsSpecsId != INVALID_RESOURCE_HANDLE
+ && inv.folderSpecsId != INVALID_RESOURCE_HANDLE
+ && inv.hotseatSpecsId != INVALID_RESOURCE_HANDLE;
- isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
+ mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
// Determine device posture.
mInfo = info;
isTablet = info.isTablet(windowBounds);
@@ -322,6 +339,8 @@
final Resources res = context.getResources();
mMetrics = res.getDisplayMetrics();
+ mIconSizeSteps = mIsResponsiveGrid ? new IconSizeSteps(res) : null;
+
// Determine sizes.
widthPx = windowBounds.bounds.width();
heightPx = windowBounds.bounds.height();
@@ -343,15 +362,11 @@
}
}
- if (mIsResponsiveGrid) {
- mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId));
- mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns,
- availableWidthPx);
- mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows,
- availableHeightPx);
- }
-
- if (DisplayController.isTransientTaskbar(context)) {
+ isTransientTaskbar = DisplayController.isTransientTaskbar(context);
+ if (!isTaskbarPresent) {
+ taskbarIconSize = taskbarHeight = stashedTaskbarHeight = taskbarBottomMargin = 0;
+ startAlignTaskbar = false;
+ } else if (isTransientTaskbar) {
float invTransientIconSizeDp = inv.transientTaskbarIconSize[mTypeIndex];
taskbarIconSize = pxFromDp(invTransientIconSizeDp, mMetrics);
taskbarHeight = Math.round((taskbarIconSize * ICON_VISIBLE_AREA_FACTOR)
@@ -373,8 +388,6 @@
edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
workspaceContentScale = res.getFloat(R.dimen.workspace_content_scale);
- desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
- desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
gridVisualizationPaddingX = res.getDimensionPixelSize(
R.dimen.grid_visualization_horizontal_cell_spacing);
gridVisualizationPaddingY = res.getDimensionPixelSize(
@@ -407,7 +420,7 @@
folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
- if (isScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) {
+ if (mIsScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) {
TypedArray folderStyle = context.obtainStyledAttributes(inv.folderStyle,
R.styleable.FolderStyle);
// These are re-set in #updateFolderCellSize if the grid is not scalable
@@ -418,19 +431,19 @@
folderContentPaddingTop = folderStyle.getDimensionPixelSize(
R.styleable.FolderStyle_folderTopPadding, 0);
- folderCellLayoutBorderSpacePx = folderStyle.getDimensionPixelSize(
+
+ int gutter = folderStyle.getDimensionPixelSize(
R.styleable.FolderStyle_folderBorderSpace, 0);
+ folderCellLayoutBorderSpacePx = new Point(gutter, gutter);
folderFooterHeightPx = folderStyle.getDimensionPixelSize(
R.styleable.FolderStyle_folderFooterHeight, 0);
folderStyle.recycle();
- } else {
- folderCellLayoutBorderSpacePx = 0;
+ } else if (!mIsResponsiveGrid) {
+ folderCellLayoutBorderSpacePx = new Point(0, 0);
folderFooterHeightPx = res.getDimensionPixelSize(R.dimen.folder_footer_height_default);
folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_top_padding_default);
}
- cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv);
- cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
allAppsBorderSpacePx = new Point(
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
@@ -449,7 +462,7 @@
cellStyle = context.obtainStyledAttributes(R.style.CellStyleDefault,
R.styleable.CellStyle);
}
- iconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
+ mIconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
R.styleable.CellStyle_iconDrawablePadding, 0);
cellStyle.recycle();
@@ -480,7 +493,7 @@
|| inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
: inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE])
&& hotseatQsbHeight > 0;
- isQsbInline = isScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline;
+ isQsbInline = mIsScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline;
areNavButtonsInline = isTaskbarPresent && !isGestureMode;
numShownHotseatIcons =
@@ -491,7 +504,17 @@
int hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
- hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+
+ if (mIsResponsiveGrid) {
+ HotseatSpecs hotseatSpecs =
+ HotseatSpecs.create(new ResourceHelper(context,
+ isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId));
+ mResponsiveHotseatSpec = hotseatSpecs.getCalculatedHeightSpec(heightPx);
+ hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace();
+ } else {
+ hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+ }
+
// Have a little space between the inset and the QSB
if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
@@ -535,6 +558,44 @@
hotseatBarEndOffset = 0;
}
+ // Needs to be calculated after hotseatBarSizePx is correct,
+ // for the available height to be correct
+ if (mIsResponsiveGrid) {
+ WorkspaceSpecs workspaceSpecs = WorkspaceSpecs.create(
+ new ResourceHelper(context,
+ isTwoPanels ? inv.workspaceSpecsTwoPanelId : inv.workspaceSpecsId));
+ int availableResponsiveWidth =
+ availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0);
+ int numColumns = getPanelCount() * inv.numColumns;
+ // don't use availableHeightPx because it subtracts bottom padding,
+ // but the workspace go behind it
+ int availableResponsiveHeight =
+ heightPx - mInsets.top - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
+ mResponsiveWidthSpec = workspaceSpecs.getCalculatedWidthSpec(numColumns,
+ availableResponsiveWidth);
+ mResponsiveHeightSpec = workspaceSpecs.getCalculatedHeightSpec(inv.numRows,
+ availableResponsiveHeight);
+
+ AllAppsSpecs allAppsSpecs = AllAppsSpecs.create(
+ new ResourceHelper(context,
+ isTwoPanels ? inv.allAppsSpecsTwoPanelId : inv.allAppsSpecsId));
+ mAllAppsResponsiveWidthSpec = allAppsSpecs.getCalculatedWidthSpec(numColumns,
+ mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
+ mAllAppsResponsiveHeightSpec = allAppsSpecs.getCalculatedHeightSpec(inv.numRows,
+ mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
+
+ FolderSpecs folderSpecs = FolderSpecs.create(
+ new ResourceHelper(context,
+ isTwoPanels ? inv.folderSpecsTwoPanelId : inv.folderSpecsId));
+ mResponsiveFolderWidthSpec = folderSpecs.getCalculatedWidthSpec(inv.numFolderColumns,
+ mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
+ mResponsiveFolderHeightSpec = folderSpecs.getCalculatedHeightSpec(inv.numFolderRows,
+ mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
+ }
+
+ desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
+ desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
+
overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
overviewTaskIconDrawableSizePx =
@@ -542,7 +603,9 @@
overviewTaskIconDrawableSizeGridPx =
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx;
- overviewActionsTopMarginPx = res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
+ // Don't add margin with floating search bar to minimize risk of overlapping.
+ overviewActionsTopMarginPx = FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() ? 0
+ : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
overviewActionsButtonSpacing = res.getDimensionPixelSize(
R.dimen.overview_actions_button_spacing);
@@ -555,21 +618,7 @@
// Calculate all of the remaining variables.
extraSpace = updateAvailableDimensions(res);
- // Now that we have all of the variables calculated, we can tune certain sizes.
- if (isScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) {
- // Paddings were created assuming no scaling, so we first unscale the extra space.
- int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
- DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId);
- DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace);
- maxEmptySpace = padding.getMaxEmptySpacePx();
-
- int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
- int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
- int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
-
- workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
- workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
- }
+ calculateAndSetWorkspaceVerticalPadding(context, inv, extraSpace);
int cellLayoutPadding =
isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize(
@@ -588,6 +637,10 @@
// Hotseat and QSB width depends on updated cellSize and workspace padding
recalculateHotseatWidthAndBorderSpace();
+ if (mIsResponsiveGrid && isVerticalBarLayout()) {
+ hotseatBorderSpace = cellLayoutBorderSpacePx.y;
+ }
+
// AllApps height calculation depends on updated cellSize
if (isTablet) {
int collapseHandleHeight =
@@ -650,19 +703,44 @@
}
private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
+ if (mIsResponsiveGrid) {
+ return mResponsiveWidthSpec.getStartPaddingPx();
+ }
+
if (isVerticalBarLayout()) {
return 0;
}
- return isScalableGrid
+ return mIsScalableGrid
? pxFromDp(idp.horizontalMargin[mTypeIndex], mMetrics)
: res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
}
+ private void calculateAndSetWorkspaceVerticalPadding(Context context,
+ InvariantDeviceProfile inv,
+ int extraSpace) {
+ if (mIsResponsiveGrid) {
+ workspaceTopPadding = mResponsiveHeightSpec.getStartPaddingPx();
+ workspaceBottomPadding = mResponsiveHeightSpec.getEndPaddingPx();
+ } else if (mIsScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) {
+ // Paddings were created assuming no scaling, so we first unscale the extra space.
+ int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
+ DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId);
+ DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace);
+ maxEmptySpace = padding.getMaxEmptySpacePx();
+
+ int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
+ int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
+
+ workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
+ workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
+ }
+ }
+
/** Updates hotseatCellHeightPx and hotseatBarSizePx */
private void updateHotseatSizes(int hotseatIconSizePx) {
// Ensure there is enough space for folder icons, which have a slightly larger radius.
- hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
+ hotseatCellHeightPx = getIconSizeWithOverlap(hotseatIconSizePx);
if (isVerticalBarLayout()) {
hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
@@ -683,7 +761,7 @@
* necessary.
*/
public void recalculateHotseatWidthAndBorderSpace() {
- if (!isScalableGrid) return;
+ if (!mIsScalableGrid) return;
int columns = inv.hotseatColumnSpan[mTypeIndex];
float hotseatWidthPx = getIconToIconWidthForColumns(columns);
@@ -728,7 +806,6 @@
hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
(isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
} while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1);
-
}
private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
@@ -736,12 +813,16 @@
}
private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
- if (!isScalableGrid) {
- return new Point(0, 0);
- }
+ int horizontalSpacePx = 0;
+ int verticalSpacePx = 0;
- int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
- int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
+ if (mIsResponsiveGrid) {
+ horizontalSpacePx = mResponsiveWidthSpec.getGutterPx();
+ verticalSpacePx = mResponsiveHeightSpec.getGutterPx();
+ } else if (mIsScalableGrid) {
+ horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
+ verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
+ }
return new Point(horizontalSpacePx, verticalSpacePx);
}
@@ -807,54 +888,29 @@
float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
- iconTextHeight;
+ if (mIsResponsiveGrid) {
+ // Hide text only if doesn't fit inside the cell for responsive grid
+ if (workspaceCellPaddingY < 0) {
+ iconTextSizePx = 0;
+ iconDrawablePaddingPx = 0;
+ int iconSizeWithOverlap = getIconSizeWithOverlap(iconSizePx);
+ cellYPaddingPx = Math.max(0, getCellSize().y - iconSizeWithOverlap) / 2;
+ autoResizeAllAppsCells();
+ }
+
+ return;
+ }
+
// We want enough space so that the text is closer to its corresponding icon.
if (workspaceCellPaddingY < iconTextHeight) {
iconTextSizePx = 0;
iconDrawablePaddingPx = 0;
- cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR);
+ cellHeightPx = getIconSizeWithOverlap(iconSizePx);
autoResizeAllAppsCells();
}
}
/**
- * Re-computes the all-apps cell size to be independent of workspace
- */
- public void autoResizeAllAppsCells() {
- int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
- int topBottomPadding = textHeight;
- allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
- + textHeight + (topBottomPadding * 2);
- }
-
- private void updateAllAppsContainerWidth(Resources res) {
- int cellLayoutHorizontalPadding =
- (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
- if (isTablet) {
- int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
- + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
- + allAppsLeftRightPadding * 2;
- allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
- } else {
- allAppsLeftRightPadding =
- desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding;
- }
- }
-
- 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.
*/
private int updateAvailableDimensions(Resources res) {
@@ -862,11 +918,15 @@
float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics));
iconTextSizePx = pxFromSp(invIconTextSizeSp, mMetrics);
+ iconCenterVertically = mIsScalableGrid || mIsResponsiveGrid;
updateIconSize(1f, res);
-
updateWorkspacePadding();
+ if (mIsResponsiveGrid) {
+ return 0;
+ }
+
// Check to see if the icons fit within the available height.
float usedHeight = getCellLayoutHeightSpecification();
final int maxHeight = getCellLayoutHeight();
@@ -875,7 +935,7 @@
boolean shouldScale = scaleY < 1f;
float scaleX = 1f;
- if (isScalableGrid) {
+ if (mIsScalableGrid) {
// We scale to fit the cellWidth and cellHeight in the available space.
// The benefit of scalable grids is that we can get consistent aspect ratios between
// devices.
@@ -906,6 +966,29 @@
+ cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
}
+ private int getNormalizedIconDrawablePadding() {
+ // TODO(b/235886078): workaround needed because of this bug
+ // Icons are 10% larger on XML than their visual size,
+ // so remove that extra space to get labels closer to the correct padding
+ int iconVisibleSizePx = (int) Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
+ return Math.max(0, mIconDrawablePaddingOriginalPx - ((iconSizePx - iconVisibleSizePx) / 2));
+ }
+
+ private int getNormalizedFolderChildDrawablePaddingPx(int textHeight) {
+ // TODO(b/235886078): workaround needed because of this bug
+ // Icons are 10% larger on XML than their visual size,
+ // so remove that extra space to get labels closer to the correct padding
+ int drawablePadding = (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3;
+
+ int iconVisibleSizePx = Math.round(ICON_VISIBLE_AREA_FACTOR * folderChildIconSizePx);
+ int iconSizeDiff = folderChildIconSizePx - iconVisibleSizePx;
+ return Math.max(0, drawablePadding - iconSizeDiff / 2);
+ }
+
+ private int getIconSizeWithOverlap(int iconSize) {
+ return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR);
+ }
+
/**
* Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
@@ -918,10 +1001,36 @@
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
- iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
- if (isScalableGrid) {
+ if (mIsResponsiveGrid) {
+ cellWidthPx = mResponsiveWidthSpec.getCellSizePx();
+ cellHeightPx = mResponsiveHeightSpec.getCellSizePx();
+
+ if (cellWidthPx < iconSizePx) {
+ // get a smaller icon size
+ iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx);
+ }
+
+ iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+ int iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
+ int cellContentHeight = iconSizePx + iconDrawablePaddingPx + iconTextHeight;
+
+ while (iconSizePx > mIconSizeSteps.minimumIconSize()
+ && cellContentHeight > cellHeightPx) {
+ iconDrawablePaddingPx -= cellContentHeight - cellHeightPx;
+ if (iconDrawablePaddingPx < 0) {
+ // get a smaller icon size
+ iconSizePx = mIconSizeSteps.getNextLowerIconSize(iconSizePx);
+ iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+ }
+ // calculate new cellContentHeight
+ cellContentHeight = iconSizePx + iconDrawablePaddingPx + iconTextHeight;
+ }
+
+ cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
+ } else if (mIsScalableGrid) {
+ iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
@@ -980,8 +1089,9 @@
desiredWorkspaceHorizontalMarginPx =
(int) (desiredWorkspaceHorizontalMarginOriginalPx * scale);
} else {
+ iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
- cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
+ cellHeightPx = getIconSizeWithOverlap(iconSizePx)
+ iconDrawablePaddingPx
+ Utilities.calculateTextHeight(iconTextSizePx);
int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
@@ -996,7 +1106,15 @@
}
// All apps
- updateAllAppsIconSize(scale, res);
+ if (mIsResponsiveGrid) {
+ updateAllAppsWithResponsiveMeasures();
+ } else {
+ updateAllAppsIconSize(scale, res);
+ }
+ updateAllAppsContainerWidth();
+ if (isVerticalBarLayout()) {
+ hideWorkspaceLabelsIfNotEnoughSpace();
+ }
updateHotseatSizes(iconSizePx);
@@ -1021,14 +1139,14 @@
* This method calculates the space between the icons to achieve a certain width.
*/
private int calculateHotseatBorderSpace(float hotseatWidthPx, int numExtraBorder) {
+ int numBorders = (numShownHotseatIcons - 1 + numExtraBorder);
+ if (numBorders <= 0) return 0;
+
float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
- int hotseatBorderSpacePx =
- (int) (hotseatWidthPx - hotseatIconsTotalPx)
- / (numShownHotseatIcons - 1 + numExtraBorder);
+ int hotseatBorderSpacePx = (int) (hotseatWidthPx - hotseatIconsTotalPx) / numBorders;
return Math.min(hotseatBorderSpacePx, mMaxHotseatIconSpacePx);
}
-
/**
* Updates the iconSize for allApps* variants.
*/
@@ -1042,10 +1160,10 @@
+ allAppsBorderSpacePx.y;
// but width is just the cell,
// the border is added in #updateAllAppsContainerWidth
- if (isScalableGrid) {
+ if (mIsScalableGrid) {
allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
- allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+ allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding();
allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
if (allAppsCellWidthPx < allAppsIconSizePx) {
@@ -1082,22 +1200,70 @@
res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
}
+ }
- updateAllAppsContainerWidth(res);
- if (isVerticalBarLayout()) {
- hideWorkspaceLabelsIfNotEnoughSpace();
+ private void updateAllAppsWithResponsiveMeasures() {
+ allAppsIconSizePx = iconSizePx;
+ allAppsIconTextSizePx = iconTextSizePx;
+ allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
+
+ allAppsBorderSpacePx = new Point(
+ mAllAppsResponsiveWidthSpec.getGutterPx(),
+ mAllAppsResponsiveHeightSpec.getGutterPx()
+ );
+ allAppsCellHeightPx = mAllAppsResponsiveHeightSpec.getCellSizePx()
+ + mAllAppsResponsiveHeightSpec.getGutterPx();
+ allAppsCellWidthPx = mAllAppsResponsiveWidthSpec.getCellSizePx();
+ allAppsLeftRightPadding = mAllAppsResponsiveWidthSpec.getStartPaddingPx();
+ }
+
+ /**
+ * Re-computes the all-apps cell size to be independent of workspace
+ */
+ public void autoResizeAllAppsCells() {
+ int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
+ int topBottomPadding = textHeight;
+ allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
+ + textHeight + (topBottomPadding * 2);
+ }
+
+ private void updateAllAppsContainerWidth() {
+ int cellLayoutHorizontalPadding =
+ (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
+ if (isTablet) {
+ int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
+ + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
+ + allAppsLeftRightPadding * 2;
+ allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
+ } else {
+ allAppsLeftRightPadding =
+ Math.max(0, desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding
+ - (allAppsBorderSpacePx.x / 2));
}
}
+ private void setupAllAppsStyle(Context context) {
+ TypedArray allAppsStyle = context.obtainStyledAttributes(
+ inv.allAppsStyle != INVALID_RESOURCE_HANDLE ? inv.allAppsStyle
+ : R.style.AllAppsStyleDefault, R.styleable.AllAppsStyle);
+
+ allAppsLeftRightPadding = allAppsStyle.getDimensionPixelSize(
+ R.styleable.AllAppsStyle_horizontalPadding, 0);
+ allAppsStyle.recycle();
+ }
+
private void updateAvailableFolderCellDimensions(Resources res) {
updateFolderCellSize(1f, res);
+ // Responsive grid doesn't need to scale the folder
+ if (mIsResponsiveGrid) return;
+
// For usability we can't have the folder use the whole width of the screen
Point totalWorkspacePadding = getTotalWorkspacePadding();
// Check if the folder fit within the available height.
float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
- + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacePx)
+ + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacePx.y)
+ folderFooterHeightPx
+ folderContentPaddingTop;
int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y;
@@ -1105,7 +1271,7 @@
// Check if the folder fit within the available width.
float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
- + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacePx)
+ + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacePx.x)
+ folderContentPaddingLeftRight * 2;
int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x;
float scaleX = contentMaxWidth / contentUsedWidth;
@@ -1125,7 +1291,45 @@
int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
- if (isScalableGrid) {
+ if (mIsResponsiveGrid) {
+ folderCellWidthPx = mResponsiveFolderWidthSpec.getCellSizePx();
+
+ // Height
+ folderCellHeightPx = mResponsiveFolderHeightSpec.getCellSizePx();
+ folderContentPaddingTop = mResponsiveFolderHeightSpec.getStartPaddingPx();
+ folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx();
+
+ folderCellLayoutBorderSpacePx = new Point(mResponsiveFolderWidthSpec.getGutterPx(),
+ mResponsiveFolderHeightSpec.getGutterPx());
+
+ folderContentPaddingLeftRight = mResponsiveFolderWidthSpec.getStartPaddingPx();
+
+ // Reduce icon width if it's wider than the expected folder cell width
+ if (folderCellWidthPx < folderChildIconSizePx) {
+ folderChildIconSizePx = mIconSizeSteps.getIconSmallerThan(folderCellWidthPx);
+ }
+
+ // Recalculating padding and cell height
+ folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
+ int folderCellContentHeight = folderChildIconSizePx + folderChildDrawablePaddingPx
+ + textHeight;
+
+ // Reduce the icon in height when it's taller than the expected cell height
+ while (folderChildIconSizePx > mIconSizeSteps.minimumIconSize()
+ && folderCellContentHeight > folderCellHeightPx) {
+ folderChildDrawablePaddingPx -= folderCellContentHeight - folderCellHeightPx;
+ if (folderChildDrawablePaddingPx < 0) {
+ // get a smaller icon size
+ folderChildIconSizePx = mIconSizeSteps.getNextLowerIconSize(
+ folderChildIconSizePx);
+ folderChildDrawablePaddingPx =
+ getNormalizedFolderChildDrawablePaddingPx(textHeight);
+ }
+ // calculate new cellContentHeight
+ folderCellContentHeight = folderChildIconSizePx + folderChildDrawablePaddingPx
+ + textHeight;
+ }
+ } else if (mIsScalableGrid) {
if (inv.folderStyle == INVALID_RESOURCE_HANDLE) {
folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale);
folderCellHeightPx = roundPxValueFromFloat(getCellSize().y * scale);
@@ -1135,11 +1339,15 @@
}
folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
- folderCellLayoutBorderSpacePx = roundPxValueFromFloat(
- folderCellLayoutBorderSpacePx * scale);
+ folderCellLayoutBorderSpacePx = new Point(
+ roundPxValueFromFloat(folderCellLayoutBorderSpacePx.x * scale),
+ roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale)
+ );
folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale);
- folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx;
+ folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
+
+ folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
} else {
int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
* scale);
@@ -1156,10 +1364,8 @@
res.getDimensionPixelSize(R.dimen.folder_footer_height_default)
* scale);
+ folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
}
-
- folderChildDrawablePaddingPx = Math.max(0,
- (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
}
public void updateInsets(Rect insets) {
@@ -1288,22 +1494,36 @@
private void updateWorkspacePadding() {
Rect padding = workspacePadding;
if (isVerticalBarLayout()) {
- padding.top = 0;
- padding.bottom = edgeMarginPx;
- if (isSeascape()) {
- padding.left = hotseatBarSizePx;
- padding.right = hotseatBarSidePaddingStartPx;
+ if (mIsResponsiveGrid) {
+ padding.top = mResponsiveHeightSpec.getStartPaddingPx();
+ padding.bottom = mResponsiveHeightSpec.getEndPaddingPx();
+ if (isSeascape()) {
+ padding.left = hotseatBarSizePx + mResponsiveWidthSpec.getEndPaddingPx();
+ padding.right = mResponsiveWidthSpec.getStartPaddingPx();
+ } else {
+ padding.left = mResponsiveWidthSpec.getStartPaddingPx();
+ padding.right = hotseatBarSizePx + mResponsiveWidthSpec.getEndPaddingPx();
+ }
} else {
- padding.left = hotseatBarSidePaddingStartPx;
- padding.right = hotseatBarSizePx;
+ padding.top = 0;
+ padding.bottom = edgeMarginPx;
+ if (isSeascape()) {
+ padding.left = hotseatBarSizePx;
+ padding.right = hotseatBarSidePaddingStartPx;
+ } else {
+ padding.left = hotseatBarSidePaddingStartPx;
+ padding.right = hotseatBarSizePx;
+ }
}
} else {
// Pad the bottom of the workspace with hotseat bar
// and leave a bit of space in case a widget go all the way down
- int paddingBottom = hotseatBarSizePx + workspaceBottomPadding
- + workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace
- - mInsets.bottom;
- int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx);
+ int paddingBottom = hotseatBarSizePx + workspaceBottomPadding - mInsets.bottom;
+ if (!mIsResponsiveGrid) {
+ paddingBottom +=
+ workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace;
+ }
+ int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx);
int paddingSide = desiredWorkspaceHorizontalMarginPx;
padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
@@ -1336,7 +1556,9 @@
// in vertical bar layout.
// Workspace icons are moved up by a small factor. The variable diffOverlapFactor
// is set to account for that difference.
- float diffOverlapFactor = iconSizePx * (ICON_OVERLAP_FACTOR - 1) / 2;
+ float diffOverlapFactor = mIsResponsiveGrid ? 0
+ : iconSizePx * (ICON_OVERLAP_FACTOR - 1) / 2;
+
int paddingTop = Math.max((int) (mInsets.top + cellLayoutPaddingPx.top
- diffOverlapFactor), 0);
int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutPaddingPx.bottom
@@ -1379,7 +1601,7 @@
hotseatBarPadding.right = endSpacing;
}
- } else if (isScalableGrid) {
+ } else if (mIsScalableGrid) {
int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
hotseatBarPadding.set(sideSpacing,
0,
@@ -1404,6 +1626,25 @@
return hotseatBarPadding;
}
+ /** The margin between the edge of all apps and the edge of the first icon. */
+ public int getAllAppsIconStartMargin() {
+ int allAppsSpacing;
+ if (isVerticalBarLayout()) {
+ // On phones, the landscape layout uses a different setup.
+ allAppsSpacing = workspacePadding.left + workspacePadding.right;
+ } else {
+ allAppsSpacing = allAppsLeftRightPadding * 2 + allAppsLeftRightMargin * 2;
+ }
+
+ int cellWidth = DeviceProfile.calculateCellWidth(
+ availableWidthPx - allAppsSpacing,
+ 0 /* borderSpace */,
+ numShownAllAppsColumns);
+ int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * allAppsIconSizePx);
+ int iconAlignmentMargin = (cellWidth - iconVisibleSize) / 2;
+ return allAppsLeftRightPadding + iconAlignmentMargin;
+ }
+
private int getAdditionalQsbSpace() {
return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
}
@@ -1599,7 +1840,7 @@
writer.println(prefix + "\taspectRatio:" + aspectRatio);
writer.println(prefix + "\tisResponsiveGrid:" + mIsResponsiveGrid);
- writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
+ writer.println(prefix + "\tisScalableGrid:" + mIsScalableGrid);
writer.println(prefix + "\tinv.numRows: " + inv.numRows);
writer.println(prefix + "\tinv.numColumns: " + inv.numColumns);
@@ -1639,8 +1880,10 @@
writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
folderChildDrawablePaddingPx));
- writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx",
- folderCellLayoutBorderSpacePx));
+ writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.x",
+ folderCellLayoutBorderSpacePx.x));
+ writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.y",
+ folderCellLayoutBorderSpacePx.y));
writer.println(prefix + pxToDpStr("folderContentPaddingLeftRight",
folderContentPaddingLeftRight));
writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop));
@@ -1753,6 +1996,17 @@
getWorkspaceSpringLoadScale(context)));
writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
+ if (mIsResponsiveGrid) {
+ writer.println(prefix + "\tmResponsiveHeightSpec:" + mResponsiveHeightSpec.toString());
+ writer.println(prefix + "\tmResponsiveWidthSpec:" + mResponsiveWidthSpec.toString());
+ writer.println(prefix + "\tmAllAppsResponsiveHeightSpec:"
+ + mAllAppsResponsiveHeightSpec.toString());
+ writer.println(prefix + "\tmAllAppsResponsiveWidthSpec:"
+ + mAllAppsResponsiveWidthSpec.toString());
+ writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec);
+ writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec);
+ writer.println(prefix + "\tmResponsiveHotseatSpec:" + mResponsiveHotseatSpec);
+ }
}
/** Returns a reduced representation of this DeviceProfile. */
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index addcac9..8bdf61a 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -30,7 +30,7 @@
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
-import com.android.launcher3.anim.Interpolators;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
@@ -42,7 +42,7 @@
implements DragListener, Insettable {
protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
- protected static final TimeInterpolator DEFAULT_INTERPOLATOR = Interpolators.ACCEL;
+ protected static final TimeInterpolator DEFAULT_INTERPOLATOR = Interpolators.ACCELERATE;
private final Runnable mFadeAnimationEndRunnable =
() -> updateVisibility(DropTargetBar.this);
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index f94a3c5..8ec5c18 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
@@ -37,6 +38,8 @@
* Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion
*/
public class ExtendedEditText extends EditText {
+ private static final String TAG = "ExtendedEditText";
+
private final Set<OnFocusChangeListener> mOnFocusChangeListeners = new HashSet<>();
private boolean mForceDisableSuggestions = false;
@@ -71,14 +74,9 @@
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
// If this is a back key, propagate the key back to the listener
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
- if (TextUtils.isEmpty(getText())) {
- hideKeyboard();
- }
- if (mBackKeyListener != null) {
- return mBackKeyListener.onBackKey();
- }
- return false;
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP
+ && mBackKeyListener != null) {
+ return mBackKeyListener.onBackKey();
}
return super.onKeyPreIme(keyCode, event);
}
@@ -89,9 +87,17 @@
return false;
}
- public void showKeyboard() {
+ /**
+ * Synchronously shows the soft input method.
+ *
+ * @param shouldFocus whether this EditText should also request focus.
+ * @return true if the keyboard is shown correctly and focus is given to this view (if
+ * applicable).
+ */
+ public boolean showKeyboard(boolean shouldFocus) {
onKeyboardShown();
- showSoftInput();
+ boolean focusResult = !shouldFocus || requestFocus();
+ return focusResult && showSoftInputInternal();
}
public void hideKeyboard() {
@@ -104,10 +110,15 @@
.keyboardStateManager().setKeyboardState(SHOW);
}
- private boolean showSoftInput() {
- return requestFocus() &&
- getContext().getSystemService(InputMethodManager.class)
- .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
+ private boolean showSoftInputInternal() {
+ boolean result = false;
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
+ if (imm != null) {
+ result = imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
+ } else {
+ Log.w(TAG, "Failed to retrieve InputMethodManager from the system.");
+ }
+ return result;
}
public void dispatchBackKey() {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 376f54d..c619bc5 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.LauncherPrefs.GRID_NAME;
import static com.android.launcher3.Utilities.dpiFromPx;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
@@ -50,6 +49,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DeviceGridState;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.testing.shared.ResourceUtils;
@@ -180,6 +180,18 @@
public int devicePaddingId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int workspaceSpecsId = INVALID_RESOURCE_HANDLE;
+ @XmlRes
+ public int workspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ @XmlRes
+ public int allAppsSpecsId = INVALID_RESOURCE_HANDLE;
+ @XmlRes
+ public int allAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ @XmlRes
+ public int folderSpecsId = INVALID_RESOURCE_HANDLE;
+ @XmlRes
+ public int folderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ public int hotseatSpecsId = INVALID_RESOURCE_HANDLE;
+ public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
public String dbFile;
public int defaultLayoutId;
@@ -275,12 +287,13 @@
* Reinitialize the current grid after a restore, where some grids might now be disabled.
*/
public void reinitializeAfterRestore(Context context) {
+ FileLog.d(TAG, "Reinitializing grid after restore");
String currentGridName = getCurrentGridName(context);
String currentDbFile = dbFile;
String newGridName = initGrid(context, currentGridName);
String newDbFile = dbFile;
if (!newDbFile.equals(currentDbFile)) {
- Log.d(TAG, "Restored grid is disabled : " + currentGridName
+ FileLog.d(TAG, "Restored grid is disabled : " + currentGridName
+ ", migrating to: " + newGridName
+ ", removing all other grid db files");
for (String gridDbFile : LauncherFiles.GRID_DB_FILES) {
@@ -288,7 +301,7 @@
continue;
}
if (context.getDatabasePath(gridDbFile).delete()) {
- Log.d(TAG, "Removed old grid db file: " + gridDbFile);
+ FileLog.d(TAG, "Removed old grid db file: " + gridDbFile);
}
}
setCurrentGrid(context, newGridName);
@@ -302,7 +315,7 @@
int type = displayInfo.supportedBounds.stream()
.mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone)
.reduce(0, (a, b) -> a | b);
- if ((type == (flagPhone | flagTablet)) && ENABLE_TWO_PANEL_HOME.get()) {
+ if (type == (flagPhone | flagTablet)) {
// device has profiles supporting both phone and table modes
return TYPE_MULTI_DISPLAY;
} else if (type == flagTablet) {
@@ -354,6 +367,13 @@
isScalable = closestProfile.isScalable;
devicePaddingId = closestProfile.devicePaddingId;
workspaceSpecsId = closestProfile.mWorkspaceSpecsId;
+ workspaceSpecsTwoPanelId = closestProfile.mWorkspaceSpecsTwoPanelId;
+ allAppsSpecsId = closestProfile.mAllAppsSpecsId;
+ allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId;
+ folderSpecsId = closestProfile.mFolderSpecsId;
+ folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId;
+ hotseatSpecsId = closestProfile.mHotseatSpecsId;
+ hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId;
this.deviceType = deviceType;
inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -800,6 +820,13 @@
private final boolean isScalable;
private final int devicePaddingId;
private final int mWorkspaceSpecsId;
+ private final int mWorkspaceSpecsTwoPanelId;
+ private final int mAllAppsSpecsId;
+ private final int mAllAppsSpecsTwoPanelId;
+ private final int mFolderSpecsId;
+ private final int mFolderSpecsTwoPanelId;
+ private final int mHotseatSpecsId;
+ private final int mHotseatSpecsTwoPanelId;
public GridOption(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(
@@ -864,8 +891,33 @@
if (FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE.get()) {
mWorkspaceSpecsId = a.getResourceId(
R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE);
+ mWorkspaceSpecsTwoPanelId = a.getResourceId(
+ R.styleable.GridDisplayOption_workspaceSpecsTwoPanelId,
+ INVALID_RESOURCE_HANDLE);
+ mAllAppsSpecsId = a.getResourceId(
+ R.styleable.GridDisplayOption_allAppsSpecsId, INVALID_RESOURCE_HANDLE);
+ mAllAppsSpecsTwoPanelId = a.getResourceId(
+ R.styleable.GridDisplayOption_allAppsSpecsTwoPanelId,
+ INVALID_RESOURCE_HANDLE);
+ mFolderSpecsId = a.getResourceId(
+ R.styleable.GridDisplayOption_folderSpecsId, INVALID_RESOURCE_HANDLE);
+ mFolderSpecsTwoPanelId = a.getResourceId(
+ R.styleable.GridDisplayOption_folderSpecsTwoPanelId,
+ INVALID_RESOURCE_HANDLE);
+ mHotseatSpecsId = a.getResourceId(
+ R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE);
+ mHotseatSpecsTwoPanelId = a.getResourceId(
+ R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId,
+ INVALID_RESOURCE_HANDLE);
} else {
mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
+ mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ mAllAppsSpecsId = INVALID_RESOURCE_HANDLE;
+ mAllAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ mFolderSpecsId = INVALID_RESOURCE_HANDLE;
+ mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ mHotseatSpecsId = INVALID_RESOURCE_HANDLE;
+ mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
}
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8fb68f1..038967c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -21,6 +21,7 @@
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
@@ -43,7 +44,6 @@
import static com.android.launcher3.LauncherState.SPRING_LOADED;
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.MULTI_SELECT_EDIT_MODE;
import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
@@ -100,7 +100,6 @@
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -145,7 +144,9 @@
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.BaseSearchConfig;
import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.PropertyListBuilder;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
@@ -154,7 +155,6 @@
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.LauncherDragController;
import com.android.launcher3.folder.Folder;
@@ -183,7 +183,6 @@
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.pm.PinRequestHelper;
-import com.android.launcher3.pm.UserCache;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
@@ -196,9 +195,11 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.CannedAnimationCoordinator;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -208,7 +209,6 @@
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
import com.android.launcher3.util.SystemUiController;
@@ -411,8 +411,6 @@
protected long mLastTouchUpTime = -1;
private boolean mTouchInProgress;
- private SafeCloseable mUserChangedCallbackCloseable;
-
// New InstanceId is assigned to mAllAppsSessionLogId for each AllApps sessions.
// When Launcher is not in AllApps state mAllAppsSessionLogId will be null.
// User actions within AllApps state are logged with this InstanceId, to recreate AllApps
@@ -425,6 +423,9 @@
private StartupLatencyLogger mStartupLatencyLogger;
private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT;
+ private final CannedAnimationCoordinator mAnimationCoordinator =
+ new CannedAnimationCoordinator(this);
+
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
@@ -445,8 +446,7 @@
Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
DISPLAY_ALL_APPS_TRACE_COOKIE);
}
- Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
- TraceHelper.FLAG_UI_EVENT);
+ TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT);
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
@@ -580,10 +580,7 @@
LauncherOverlayPlugin.class, false /* allowedMultiple */);
mRotationHelper.initialize();
- TraceHelper.INSTANCE.endSection(traceToken);
-
- mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
- () -> getStateManager().goToState(NORMAL));
+ TraceHelper.INSTANCE.endSection();
if (Utilities.ATLEAST_R) {
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
@@ -733,18 +730,23 @@
@Override
protected void onHandleConfigurationChanged() {
- if (!initDeviceProfile(mDeviceProfile.inv)) {
- return;
+ Trace.beginSection("Launcher#onHandleconfigurationChanged");
+ try {
+ if (!initDeviceProfile(mDeviceProfile.inv)) {
+ return;
+ }
+
+ dispatchDeviceProfileChanged();
+ reapplyUi();
+ mDragLayer.recreateControllers();
+
+ // Calling onSaveInstanceState ensures that static cache used by listWidgets is
+ // initialized properly.
+ onSaveInstanceState(new Bundle());
+ mModel.rebindCallbacks();
+ } finally {
+ Trace.endSection();
}
-
- dispatchDeviceProfileChanged();
- reapplyUi();
- mDragLayer.recreateControllers();
-
- // Calling onSaveInstanceState ensures that static cache used by listWidgets is
- // initialized properly.
- onSaveInstanceState(new Bundle());
- mModel.rebindCallbacks();
}
public void onAssistantVisibilityChanged(float visibility) {
@@ -1079,15 +1081,14 @@
@Override
protected void onStart() {
- Object traceToken = TraceHelper.INSTANCE.beginSection(ON_START_EVT,
- TraceHelper.FLAG_UI_EVENT);
+ TraceHelper.INSTANCE.beginSection(ON_START_EVT);
super.onStart();
if (!mDeferOverlayCallbacks) {
mOverlayManager.onActivityStarted(this);
}
mAppWidgetHolder.setActivityStarted(true);
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
@Override
@@ -1258,8 +1259,7 @@
@Override
protected void onResume() {
- Object traceToken = TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT,
- TraceHelper.FLAG_UI_EVENT);
+ TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
super.onResume();
if (mDeferOverlayCallbacks) {
@@ -1269,7 +1269,7 @@
}
DragView.removeAllViews(this);
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
@Override
@@ -1402,9 +1402,10 @@
* @param info The data structure describing the shortcut.
* @return A View inflated from layoutResId.
*/
- public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) {
- BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
- .inflate(R.layout.app_icon, parent, false);
+ public View createShortcut(@Nullable ViewGroup parent, WorkspaceItemInfo info) {
+ BubbleTextView favorite =
+ (BubbleTextView) LayoutInflater.from(parent != null ? parent.getContext() : this)
+ .inflate(R.layout.app_icon, parent, false);
favorite.applyFromWorkspaceItem(info);
favorite.setOnClickListener(getItemOnClickListener());
favorite.setOnFocusChangeListener(mFocusHandler);
@@ -1656,7 +1657,7 @@
if (Utilities.isRunningInTestHarness()) {
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
}
- Object traceToken = TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
+ TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
super.onNewIntent(intent);
boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() &
@@ -1672,7 +1673,8 @@
if (isActionMain) {
if (!internalStateHandled) {
// In all these cases, only animate if we're already on home
- closeOpenViews(isStarted());
+ AbstractFloatingView.closeAllOpenViewsExcept(
+ this, isStarted(), AbstractFloatingView.TYPE_LISTENER);
if (!isInState(NORMAL)) {
// Only change state, if not already the same. This prevents cancelling any
@@ -1693,6 +1695,9 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onHomeIntent(internalStateHandled);
}
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ handleSplitAnimationGoingToHome();
+ }
mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
handleGestureContract(intent);
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
@@ -1703,14 +1708,28 @@
showAllAppsWorkTabFromIntent(alreadyOnHome);
}
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
+ }
+
+ /** Handle animating away split placeholder view when user taps on home button */
+ protected void handleSplitAnimationGoingToHome() {
+ // Overridden
}
protected void toggleAllAppsFromIntent(boolean alreadyOnHome) {
if (getStateManager().isInStableState(ALL_APPS)) {
getStateManager().goToState(NORMAL, alreadyOnHome);
} else {
- showAllAppsFromIntent(alreadyOnHome);
+ AbstractFloatingView.closeAllOpenViews(this);
+ getStateManager().goToState(ALL_APPS, true /* animated */,
+ new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (mAppsView.getSearchUiManager().getEditText() != null) {
+ mAppsView.getSearchUiManager().getEditText().requestFocus();
+ }
+ }
+ });
}
}
@@ -1807,7 +1826,6 @@
LauncherAppState.getIDP(this).removeOnChangeListener(this);
mOverlayManager.onActivityDestroyed(this);
- mUserChangedCallbackCloseable.close();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -2298,7 +2316,7 @@
* Implementation of the method from LauncherModel.Callbacks.
*/
public void startBinding() {
- Object traceToken = TraceHelper.INSTANCE.beginSection("startBinding");
+ TraceHelper.INSTANCE.beginSection("startBinding");
// Floating panels (except the full widget sheet) are associated with individual icons. If
// we are starting a fresh bind, close all such panels as all the icons are about
// to go away.
@@ -2316,11 +2334,12 @@
if (mHotseat != null) {
mHotseat.resetLayout(getDeviceProfile().isVerticalBarLayout());
}
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
@Override
public void bindScreens(IntArray orderedScreenIds) {
+ mWorkspace.mPageIndicator.setAreScreensBinding(true);
int firstScreenPosition = 0;
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
@@ -2338,13 +2357,37 @@
mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
}
+ /**
+ * Remove odd number because they are already included when isTwoPanels and add the pair screen
+ * if not present.
+ */
+ private IntArray filterTwoPanelScreenIds(IntArray orderedScreenIds) {
+ IntSet screenIds = IntSet.wrap(orderedScreenIds);
+ orderedScreenIds.forEach(screenId -> {
+ if (screenId % 2 == 1) {
+ screenIds.remove(screenId);
+ // In case the pair is not added, add it
+ if (!mWorkspace.containsScreenId(screenId - 1)) {
+ screenIds.add(screenId - 1);
+ }
+ }
+ });
+ return screenIds.getArray();
+ }
+
private void bindAddScreens(IntArray orderedScreenIds) {
+
if (mDeviceProfile.isTwoPanels) {
- // Some empty pages might have been removed while the phone was in a single panel
- // mode, so we want to add those empty pages back.
- IntSet screenIds = IntSet.wrap(orderedScreenIds);
- orderedScreenIds.forEach(screenId -> screenIds.add(mWorkspace.getScreenPair(screenId)));
- orderedScreenIds = screenIds.getArray();
+ if (FOLDABLE_SINGLE_PAGE.get()) {
+ orderedScreenIds = filterTwoPanelScreenIds(orderedScreenIds);
+ } else {
+ // Some empty pages might have been removed while the phone was in a single panel
+ // mode, so we want to add those empty pages back.
+ IntSet screenIds = IntSet.wrap(orderedScreenIds);
+ orderedScreenIds.forEach(
+ screenId -> screenIds.add(mWorkspace.getScreenPair(screenId)));
+ orderedScreenIds = screenIds.getArray();
+ }
}
int count = orderedScreenIds.size();
@@ -2435,7 +2478,6 @@
final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
WorkspaceItemInfo info = (WorkspaceItemInfo) item;
view = createShortcut(info);
@@ -2448,9 +2490,9 @@
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
- FolderInfo info = (FolderInfo) item;
- // TODO (jeremysim b/274189428): Create app pair icon
- view = null;
+ view = AppPairIcon.inflateIcon(R.layout.app_pair_icon, this,
+ (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
+ (FolderInfo) item);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -2570,7 +2612,7 @@
return view;
}
- Object traceToken = TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
+ TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
try {
final LauncherAppWidgetProviderInfo appWidgetInfo;
@@ -2700,7 +2742,7 @@
}
prepareAppWidget(view, item);
} finally {
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
return view;
@@ -2793,7 +2835,7 @@
* Implementation of the method from LauncherModel.Callbacks.
*/
public void finishBindingItems(IntSet pagesBoundFirst) {
- Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
+ TraceHelper.INSTANCE.beginSection("finishBindingItems");
mWorkspace.restoreInstanceStateForRemainingPages();
setWorkspaceLoading(false);
@@ -2818,9 +2860,9 @@
mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
getViewCache().setCacheSize(R.layout.folder_page, 2);
- TraceHelper.INSTANCE.endSection(traceToken);
-
- mWorkspace.removeExtraEmptyScreen(true);
+ TraceHelper.INSTANCE.endSection();
+ mWorkspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true);
+ mWorkspace.mPageIndicator.setAreScreensBinding(false);
}
private boolean canAnimatePageChange() {
@@ -2974,9 +3016,14 @@
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
- AllAppsStore appsStore = mAppsView.getAppsStore();
+ boolean hadWorkApps = mAppsView.shouldShowTabs();
+ AllAppsStore<Launcher> appsStore = mAppsView.getAppsStore();
appsStore.setApps(apps, flags, packageUserKeytoUidMap);
PopupContainerWithArrow.dismissInvalidPopup(this);
+ if (hadWorkApps != mAppsView.shouldShowTabs()) {
+ getStateManager().goToState(NORMAL);
+ }
+
if (Utilities.ATLEAST_S) {
Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
DISPLAY_ALL_APPS_TRACE_COOKIE);
@@ -3240,8 +3287,6 @@
return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
}
- public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
-
public void onDragLayerHierarchyChanged() {
updateDisallowBack();
}
@@ -3307,10 +3352,6 @@
return false;
}
- public DragOptions getDefaultWorkspaceDragOptions() {
- return new DragOptions();
- }
-
/**
* Animates Launcher elements during a transition to the All Apps page.
*
@@ -3382,4 +3423,24 @@
public boolean areFreeformTasksVisible() {
return false; // Base launcher does not track freeform tasks
}
+
+ @Override
+ public View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return ItemLongClickListener.INSTANCE_ALL_APPS;
+ }
+
+ /**
+ * Handles an app pair launch; overridden in
+ * {@link com.android.launcher3.uioverrides.QuickstepLauncher}
+ */
+ public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
+ // Overridden
+ }
+
+ /**
+ * Returns the animation coordinator for playing one-off animations
+ */
+ public CannedAnimationCoordinator getAnimationCoordinator() {
+ return mAnimationCoordinator;
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 4d15ac7..9db8c82 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+import static android.content.Context.RECEIVER_EXPORTED;
import static com.android.launcher3.LauncherPrefs.ICON_STATE;
import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
@@ -26,6 +27,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.LauncherApps;
@@ -38,7 +40,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
@@ -62,6 +63,7 @@
public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
public static final String KEY_ICON_STATE = "pref_icon_shape_path";
+ public static final String KEY_ALL_APPS_OVERVIEW_THRESHOLD = "pref_all_apps_overview_threshold";
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
@@ -104,27 +106,27 @@
});
mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
+ mOnTerminateCallback.add(() ->
+ mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel));
SimpleBroadcastReceiver modelChangeReceiver =
new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
- Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
- Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
- Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
- Intent.ACTION_PROFILE_INACCESSIBLE,
ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
- if (FeatureFlags.IS_STUDIO_BUILD) {
- modelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
+ if (BuildConfig.IS_STUDIO_BUILD) {
+ mContext.registerReceiver(modelChangeReceiver, new IntentFilter(ACTION_FORCE_ROLOAD),
+ RECEIVER_EXPORTED);
}
mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
- .addUserChangeListener(mModel::forceReload);
+ .addUserEventListener(mModel::onUserEvent);
mOnTerminateCallback.add(userChangeListener::close);
LockedUserState.get(context).runOnUserUnlocked(() -> {
- CustomWidgetManager.INSTANCE.get(mContext)
- .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
+ CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
+ cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
+ mOnTerminateCallback.add(() -> cwm.setWidgetRefreshCallback(null));
IconObserver observer = new IconObserver();
SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
@@ -159,6 +161,7 @@
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
+ mOnTerminateCallback.add(mModel::destroy);
}
private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -180,9 +183,6 @@
*/
@Override
public void close() {
- mModel.destroy();
- mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
- CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
mOnTerminateCallback.executeAllAndDestroy();
}
diff --git a/src/com/android/launcher3/LauncherBackupAgent.java b/src/com/android/launcher3/LauncherBackupAgent.java
index 3d2700d..2617b93 100644
--- a/src/com/android/launcher3/LauncherBackupAgent.java
+++ b/src/com/android/launcher3/LauncherBackupAgent.java
@@ -34,7 +34,7 @@
// Remove old files which might contain obsolete attributes like idp_grid_name in shared
// preference that will obstruct backup's attribute from writing to shared preferences.
if (destination.delete()) {
- FileLog.d("LauncherBackupAgent", "Removed obsolete file: " + destination);
+ FileLog.d(TAG, "onRestoreFile: Removed obsolete file " + destination);
}
super.onRestoreFile(data, size, destination, type, mode, mtime);
}
@@ -47,6 +47,7 @@
@Override
public void onRestoreFinished() {
+ FileLog.d(TAG, "onRestoreFinished: set pending for RestoreDbTask");
RestoreDbTask.setPending(this);
}
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 4e066b0..fd8f668 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -20,9 +20,7 @@
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -69,7 +67,6 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey;
@@ -95,15 +92,6 @@
static final String TAG = "Launcher.Model";
- // Broadcast intent to track when the profile gets locked:
- // ACTION_MANAGED_PROFILE_UNAVAILABLE can be used until Android U where profile no longer gets
- // locked when paused.
- // ACTION_PROFILE_INACCESSIBLE always means that the profile is getting locked but it only
- // appeared in Android S.
- private static final String ACTION_PROFILE_LOCKED = Utilities.ATLEAST_U
- ? Intent.ACTION_PROFILE_INACCESSIBLE
- : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
-
@NonNull
private final LauncherAppState mApp;
@NonNull
@@ -114,6 +102,9 @@
private LoaderTask mLoaderTask;
private boolean mIsLoaderTaskRunning;
+ // only allow this once per reboot to reload work apps
+ private boolean mShouldReloadWorkProfile = true;
+
// Indicates whether the current model data is valid or not.
// We start off with everything not loaded. After that, we assume that
// our monitoring of the package manager provides all updates and we never
@@ -143,6 +134,8 @@
@NonNull
private final ModelDelegate mModelDelegate;
+ private int mLastLoadId = -1;
+
// Runnable to check if the shortcuts permission has changed.
@NonNull
private final Runnable mDataValidationCheck = new Runnable() {
@@ -301,28 +294,6 @@
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
- } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)
- || Intent.ACTION_PROFILE_INACCESSIBLE.equals(action)) {
- UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.WORK_TAB_MISSING, "onBroadcastIntent intentAction: " + action +
- " user: " + user);
- }
- if (user != null) {
- if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
- Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
- enqueueModelUpdateTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
- }
-
- if (ACTION_PROFILE_LOCKED.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
- enqueueModelUpdateTask(new UserLockStateChangedTask(
- user, Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)));
- }
- }
} else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) {
enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate));
} else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
@@ -335,6 +306,30 @@
}
/**
+ * Called then there use a user event
+ * @see UserCache#addUserEventListener
+ */
+ public void onUserEvent(UserHandle user, String action) {
+ if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
+ && mShouldReloadWorkProfile) {
+ mShouldReloadWorkProfile = false;
+ forceReload();
+ } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
+ || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
+ mShouldReloadWorkProfile = false;
+ enqueueModelUpdateTask(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
+ } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action)
+ || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) {
+ enqueueModelUpdateTask(new UserLockStateChangedTask(
+ user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action)));
+ } else if (UserCache.ACTION_PROFILE_ADDED.equals(action)
+ || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
+ forceReload();
+ }
+ }
+
+ /**
* Reloads the workspace items from the DB and re-binds the workspace. This should generally
* not be called as DB updates are automatically followed by UI update
*/
@@ -553,6 +548,7 @@
if (mLoaderTask != task) {
throw new CancellationException("Loader already stopped");
}
+ mLastLoadId++;
mTask = task;
mIsLoaderTaskRunning = true;
mModelLoaded = false;
@@ -563,7 +559,6 @@
synchronized (mLock) {
// Everything loaded bind the data.
mModelLoaded = true;
- testLogD(WORK_TAB_MISSING, "launcher model loaded");
}
}
@@ -722,4 +717,12 @@
return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
}
}
+
+ /**
+ * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
+ * transaction should be ignored.
+ */
+ public int getLastLoadId() {
+ return mLastLoadId;
+ }
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index c98df1b..427eaa3 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -275,6 +275,9 @@
const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
@JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
+ @JvmField
+ val ALL_APPS_OVERVIEW_THRESHOLD =
+ nonRestorableItem(LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD, 180, true)
@JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
@JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 9abec50..440e146 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,21 +16,18 @@
package com.android.launcher3;
-import android.annotation.TargetApi;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentProvider;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
-import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
@@ -38,12 +35,11 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.widget.LauncherWidgetHolder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
+import java.util.function.ToIntFunction;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "LauncherProvider";
@@ -74,10 +70,6 @@
return true;
}
- public ModelDbController getModelDbController() {
- return LauncherAppState.getInstance(getContext()).getModel().getModelDbController();
- }
-
@Override
public String getType(Uri uri) {
SqlArguments args = new SqlArguments(uri, null, null);
@@ -91,180 +83,91 @@
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
-
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(args.table);
- Cursor result = getModelDbController().query(
- args.table, projection, args.where, args.args, sortOrder);
- result.setNotificationUri(getContext().getContentResolver(), uri);
- return result;
- }
-
- private void reloadLauncherIfExternal() {
- if (Binder.getCallingPid() != Process.myPid()) {
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app != null) {
- app.getModel().forceReload();
- }
- }
+ Cursor[] result = new Cursor[1];
+ executeControllerTask(controller -> {
+ result[0] = controller.query(args.table, projection, args.where, args.args, sortOrder);
+ return 0;
+ });
+ return result[0];
}
@Override
- public Uri insert(Uri uri, ContentValues initialValues) {
- // In very limited cases, we support system|signature permission apps to modify the db.
- if (Binder.getCallingPid() != Process.myPid()) {
- if (!initializeExternalAdd(initialValues)) {
- return null;
- }
- }
+ public Uri insert(Uri uri, ContentValues values) {
+ int rowId = executeControllerTask(controller -> {
+ // 1. Ensure that externally added items have a valid item id
+ int id = controller.generateNewItemId();
+ values.put(LauncherSettings.Favorites._ID, id);
- SqlArguments args = new SqlArguments(uri);
- int rowId = getModelDbController().insert(args.table, initialValues);
- if (rowId < 0) return null;
+ // 2. In the case of an app widget, and if no app widget id is specified, we
+ // attempt allocate and bind the widget.
+ Integer itemType = values.getAsInteger(Favorites.ITEM_TYPE);
+ if (itemType != null
+ && itemType.intValue() == Favorites.ITEM_TYPE_APPWIDGET
+ && !values.containsKey(Favorites.APPWIDGET_ID)) {
- uri = ContentUris.withAppendedId(uri, rowId);
- reloadLauncherIfExternal();
- return uri;
- }
+ ComponentName cn = ComponentName.unflattenFromString(
+ values.getAsString(Favorites.APPWIDGET_PROVIDER));
+ if (cn == null) {
+ return 0;
+ }
- private boolean initializeExternalAdd(ContentValues values) {
- // 1. Ensure that externally added items have a valid item id
- int id = getModelDbController().generateNewItemId();
- values.put(LauncherSettings.Favorites._ID, id);
-
- // 2. In the case of an app widget, and if no app widget id is specified, we
- // attempt allocate and bind the widget.
- Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
- if (itemType != null &&
- itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
- !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
-
- final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
- ComponentName cn = ComponentName.unflattenFromString(
- values.getAsString(Favorites.APPWIDGET_PROVIDER));
-
- if (cn != null) {
LauncherWidgetHolder widgetHolder = LauncherWidgetHolder.newInstance(getContext());
try {
int appWidgetId = widgetHolder.allocateAppWidgetId();
values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
- if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
+ if (!AppWidgetManager.getInstance(getContext())
+ .bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
widgetHolder.deleteAppWidgetId(appWidgetId);
- return false;
+ return 0;
}
} catch (RuntimeException e) {
Log.e(TAG, "Failed to initialize external widget", e);
- return false;
+ return 0;
} finally {
// Necessary to destroy the holder to free up possible activity context
widgetHolder.destroy();
}
- } else {
- return false;
}
- }
- return true;
- }
+ SqlArguments args = new SqlArguments(uri);
+ return controller.insert(args.table, values);
+ });
- @Override
- public int bulkInsert(Uri uri, ContentValues[] values) {
- SqlArguments args = new SqlArguments(uri);
- getModelDbController().bulkInsert(args.table, values);
- reloadLauncherIfExternal();
- return values.length;
- }
-
- @TargetApi(Build.VERSION_CODES.M)
- @Override
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
- throws OperationApplicationException {
- try (SQLiteTransaction t = getModelDbController().newTransaction()) {
- final int numOperations = operations.size();
- final ContentProviderResult[] results = new ContentProviderResult[numOperations];
- for (int i = 0; i < numOperations; i++) {
- ContentProviderOperation op = operations.get(i);
- results[i] = op.apply(this, results, i);
- }
- t.commit();
- reloadLauncherIfExternal();
- return results;
- }
+ return rowId < 0 ? null : ContentUris.withAppendedId(uri, rowId);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
- int count = getModelDbController().delete(args.table, args.where, args.args);
- if (count > 0) {
- reloadLauncherIfExternal();
- }
- return count;
+ return executeControllerTask(c -> c.delete(args.table, args.where, args.args));
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
- int count = getModelDbController().update(args.table, values, args.where, args.args);
- reloadLauncherIfExternal();
- return count;
+ return executeControllerTask(c -> c.update(args.table, values, args.where, args.args));
}
- @Override
- public Bundle call(String method, final String arg, final Bundle extras) {
- if (Binder.getCallingUid() != Process.myUid()) {
- return null;
+ private int executeControllerTask(ToIntFunction<ModelDbController> task) {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ throw new IllegalArgumentException("Same process should call model directly");
}
-
- switch (method) {
- case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
- getModelDbController().clearEmptyDbFlag();
- return null;
- }
- case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
- Bundle result = new Bundle();
- result.putIntArray(LauncherSettings.Settings.EXTRA_VALUE,
- getModelDbController().deleteEmptyFolders().toArray());
- return result;
- }
- case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: {
- Bundle result = new Bundle();
- result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
- getModelDbController().generateNewItemId());
- return result;
- }
- case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
- Bundle result = new Bundle();
- result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
- getModelDbController().getNewScreenId());
- return result;
- }
- case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
- getModelDbController().createEmptyDB();
- return null;
- }
- case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
- getModelDbController().loadDefaultFavoritesIfNecessary();
- return null;
- }
- case LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS: {
- getModelDbController().removeGhostWidgets();
- return null;
- }
- case LauncherSettings.Settings.METHOD_NEW_TRANSACTION: {
- Bundle result = new Bundle();
- result.putBinder(LauncherSettings.Settings.EXTRA_VALUE,
- getModelDbController().newTransaction());
- return result;
- }
- case LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE: {
- getModelDbController().refreshHotseatRestoreTable();
- return null;
- }
+ try {
+ return MODEL_EXECUTOR.submit(() -> {
+ LauncherModel model = LauncherAppState.getInstance(getContext()).getModel();
+ int count = task.applyAsInt(model.getModelDbController());
+ if (count > 0) {
+ MAIN_EXECUTOR.submit(model::forceReload);
+ }
+ return count;
+ }).get();
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
}
- return null;
}
static class SqlArguments {
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 7fda326..105d5f3 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -16,10 +16,7 @@
package com.android.launcher3;
-import android.content.ContentResolver;
import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.Bundle;
import android.provider.BaseColumns;
import com.android.launcher3.model.data.ItemInfo;
@@ -89,7 +86,9 @@
/**
* The gesture is an application created shortcut
+ * @deprecated This is no longer supported. Use {@link #ITEM_TYPE_DEEP_SHORTCUT} instead
*/
+ @Deprecated
public static final int ITEM_TYPE_SHORTCUT = 1;
/**
@@ -153,24 +152,6 @@
public static final String TMP_TABLE = "favorites_tmp";
/**
- * The content:// style URL for "favorites" table
- */
- public static final Uri CONTENT_URI = Uri.parse("content://"
- + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
-
- /**
- * The content:// style URL for a given row, identified by its id.
- *
- * @param id The row id.
- *
- * @return The unique content URL for the specified row.
- */
- public static Uri getContentUri(int id) {
- return Uri.parse("content://" + LauncherProvider.AUTHORITY
- + "/" + TABLE_NAME + "/" + id);
- }
-
- /**
* The container holding the favorite
* <P>Type: INTEGER</P>
*/
@@ -213,7 +194,6 @@
public static final String itemTypeToString(int type) {
switch(type) {
case ITEM_TYPE_APPLICATION: return "APP";
- case ITEM_TYPE_SHORTCUT: return "SHORTCUT";
case ITEM_TYPE_FOLDER: return "FOLDER";
case ITEM_TYPE_APPWIDGET: return "WIDGET";
case ITEM_TYPE_CUSTOM_APPWIDGET: return "CUSTOMWIDGET";
@@ -338,42 +318,8 @@
* Launcher settings
*/
public static final class Settings {
-
- public static final Uri CONTENT_URI = Uri.parse("content://" +
- LauncherProvider.AUTHORITY + "/settings");
-
- public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag";
-
- public static final String METHOD_DELETE_EMPTY_FOLDERS = "delete_empty_folders";
-
- public static final String METHOD_NEW_ITEM_ID = "generate_new_item_id";
- public static final String METHOD_NEW_SCREEN_ID = "generate_new_screen_id";
-
- public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
-
- public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
-
- public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
-
- public static final String METHOD_NEW_TRANSACTION = "new_db_transaction";
-
- public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
-
- public static final String EXTRA_VALUE = "value";
-
- public static final String EXTRA_DB_NAME = "db_name";
-
public static final String LAYOUT_DIGEST_KEY = "launcher3.layout.provider.blob";
public static final String LAYOUT_DIGEST_LABEL = "launcher-layout";
public static final String LAYOUT_DIGEST_TAG = "ignore";
-
- public static Bundle call(ContentResolver cr, String method) {
- return call(cr, method, null /* arg */);
- }
-
- public static Bundle call(ContentResolver cr, String method, String arg) {
- return cr.call(CONTENT_URI, method, arg, null);
- }
-
}
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 6097fb1..d2ea7cc 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -15,8 +15,8 @@
*/
package com.android.launcher3;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.app.animation.Interpolators.ACCELERATE_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
@@ -33,6 +33,7 @@
import android.content.Context;
import android.graphics.Color;
+import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
@@ -66,6 +67,7 @@
public static final int CLEAR_ALL_BUTTON = 1 << 4;
public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
+ public static final int FLOATING_SEARCH_BAR = 1 << 7;
// Flag indicating workspace has multiple pages visible.
public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
@@ -90,7 +92,7 @@
public static final float NO_SCALE = 1;
protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER =
- new PageAlphaProvider(ACCEL_2) {
+ new PageAlphaProvider(ACCELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
return 1;
@@ -98,7 +100,7 @@
};
protected static final PageTranslationProvider DEFAULT_PAGE_TRANSLATION_PROVIDER =
- new PageTranslationProvider(DEACCEL_2) {
+ new PageTranslationProvider(DECELERATE_2) {
@Override
public float getPageTranslation(int pageIndex) {
return 0;
@@ -202,8 +204,61 @@
return 0;
}
+ /**
+ * How far from the bottom of the screen the <em>floating</em> search bar should rest in this
+ * state when the IME is not present.
+ * <p>
+ * To hide offscreen, use a negative value.
+ * <p>
+ * Note: if the provided value is non-negative but less than the current bottom insets, the
+ * insets will be applied. As such, you can use 0 to default to this.
+ */
+ public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return areElementsVisible(launcher, FLOATING_SEARCH_BAR) ? dp.getQsbOffsetY()
+ : -dp.hotseatQsbHeight;
+ }
+
+ /**
+ * How far from the start of the screen the <em>floating</em> search bar should rest.
+ * <p>
+ * To use original margin, return a negative value.
+ */
+ public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
+ boolean isRtl = Utilities.isRtl(launcher.getResources());
+ View qsb = launcher.getHotseat().getQsb();
+ return isRtl ? launcher.getHotseat().getRight() - qsb.getRight() : qsb.getLeft();
+ }
+
+ /**
+ * How far from the end of the screen the <em>floating</em> search bar should rest.
+ * <p>
+ * To use original margin, return a negative value.
+ */
+ public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.isQsbInline) {
+ int marginStart = getFloatingSearchBarRestingMarginStart(launcher);
+ return dp.widthPx - marginStart - dp.hotseatQsbWidth;
+ }
+
+ boolean isRtl = Utilities.isRtl(launcher.getResources());
+ View qsb = launcher.getHotseat().getQsb();
+ return isRtl ? qsb.getLeft() : launcher.getHotseat().getRight() - qsb.getRight();
+ }
+
+ /** Whether the <em>floating</em> search bar should use the pill UI when not focused. */
+ public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
+ return false;
+ }
+
public int getVisibleElements(Launcher launcher) {
- return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR;
+ int elements = HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR;
+ // Floating search bar is visible in normal state except in landscape on phones.
+ if (!(launcher.getDeviceProfile().isPhone && launcher.getDeviceProfile().isLandscape)) {
+ elements |= FLOATING_SEARCH_BAR;
+ }
+ return elements;
}
/**
@@ -319,7 +374,7 @@
return DEFAULT_ALPHA_PROVIDER;
}
final int centerPage = launcher.getWorkspace().getNextPage();
- return new PageAlphaProvider(ACCEL_2) {
+ return new PageAlphaProvider(ACCELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
return pageIndex != centerPage ? 0 : 1f;
@@ -336,7 +391,7 @@
return DEFAULT_PAGE_TRANSLATION_PROVIDER;
}
final float quarterPageSpacing = launcher.getWorkspace().getPageSpacing() / 4f;
- return new PageTranslationProvider(DEACCEL_2) {
+ return new PageTranslationProvider(DECELERATE_2) {
@Override
public float getPageTranslation(int pageIndex) {
boolean isRtl = launcher.getWorkspace().mIsRtl;
diff --git a/src/com/android/launcher3/MotionEventsUtils.java b/src/com/android/launcher3/MotionEventsUtils.java
index 40de003..3228ec6 100644
--- a/src/com/android/launcher3/MotionEventsUtils.java
+++ b/src/com/android/launcher3/MotionEventsUtils.java
@@ -30,6 +30,9 @@
/** {@link MotionEvent#CLASSIFICATION_MULTI_FINGER_SWIPE} is hidden. */
public static final int CLASSIFICATION_MULTI_FINGER_SWIPE = 4;
+ /** {@link MotionEvent#AXIS_GESTURE_SWIPE_FINGER_COUNT} is hidden. */
+ private static final int AXIS_GESTURE_SWIPE_FINGER_COUNT = 53;
+
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static boolean isTrackpadScroll(MotionEvent event) {
return ENABLE_TRACKPAD_GESTURE.get()
@@ -43,11 +46,13 @@
}
public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
- return isTrackpadMultiFingerSwipe(event) && event.getPointerCount() == 3;
+ return isTrackpadMultiFingerSwipe(event) && event.getAxisValue(
+ AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
}
public static boolean isTrackpadFourFingerSwipe(MotionEvent event) {
- return isTrackpadMultiFingerSwipe(event) && event.getPointerCount() == 4;
+ return isTrackpadMultiFingerSwipe(event) && event.getAxisValue(
+ AXIS_GESTURE_SWIPE_FINGER_COUNT) == 4;
}
public static boolean isTrackpadMotionEvent(MotionEvent event) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index af64b3b..4b4a4a5 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,7 +16,7 @@
package com.android.launcher3;
-import static com.android.launcher3.anim.Interpolators.SCROLL;
+import static com.android.app.animation.Interpolators.SCROLL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index aaccb7d..d460ba8 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -32,6 +32,8 @@
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.Executors;
+import java.util.Locale;
+
/**
* BroadcastReceiver to handle session commit intent.
*/
@@ -63,9 +65,20 @@
}
InstallSessionHelper packageInstallerCompat = InstallSessionHelper.INSTANCE.get(context);
+ boolean alreadyAddedPromiseIcon =
+ packageInstallerCompat.promiseIconAddedForId(info.getSessionId());
if (TextUtils.isEmpty(info.getAppPackageName())
|| info.getInstallReason() != PackageManager.INSTALL_REASON_USER
- || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
+ || alreadyAddedPromiseIcon) {
+ FileLog.d(LOG,
+ String.format(Locale.ENGLISH,
+ "Removing PromiseIcon for package: %s, install reason: %d,"
+ + " alreadyAddedPromiseIcon: %s",
+ info.getAppPackageName(),
+ info.getInstallReason(),
+ alreadyAddedPromiseIcon
+ )
+ );
packageInstallerCompat.removePromiseIconId(info.getSessionId());
return;
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index a0ceefb..f0fea61 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -28,6 +28,7 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Trace;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -65,6 +66,7 @@
mActivity = ActivityContext.lookupContext(context);
mWallpaperManager = WallpaperManager.getInstance(context);
mContainerType = containerType;
+ setClipChildren(false);
}
public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY,
@@ -154,14 +156,15 @@
mBorderSpace);
// Center the icon/folder
int cHeight = getCellContentHeight();
- int cellPaddingY = dp.isScalableGrid && mContainerType == WORKSPACE
- ? dp.cellYPaddingPx
- : (int) Math.max(0, ((lp.height - cHeight) / 2f));
+ int cellPaddingY =
+ dp.cellYPaddingPx >= 0 && mContainerType == WORKSPACE
+ ? dp.cellYPaddingPx
+ : (int) Math.max(0, ((lp.height - cHeight) / 2f));
// No need to add padding when cell layout border spacing is present.
boolean noPaddingX =
(dp.cellLayoutBorderSpacePx.x > 0 && mContainerType == WORKSPACE)
- || (dp.folderCellLayoutBorderSpacePx > 0 && mContainerType == FOLDER)
+ || (dp.folderCellLayoutBorderSpacePx.x > 0 && mContainerType == FOLDER)
|| (dp.hotseatBorderSpace > 0 && mContainerType == HOTSEAT);
int cellPaddingX = noPaddingX
? 0
@@ -181,6 +184,7 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ Trace.beginSection("ShortcutAndWidgetConteiner#onLayout");
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
@@ -188,6 +192,7 @@
layoutChild(child);
}
}
+ Trace.endSection();
}
/**
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 709c57c..e8c6ff9 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -24,6 +24,7 @@
import android.annotation.TargetApi;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.Person;
import android.app.WallpaperManager;
import android.content.Context;
@@ -333,14 +334,12 @@
public static void scaleRectAboutCenter(Rect r, float scale) {
if (scale != 1.0f) {
- int cx = r.centerX();
- int cy = r.centerY();
- r.offset(-cx, -cy);
- r.left = (int) (r.left * scale + 0.5f);
- r.top = (int) (r.top * scale + 0.5f);
- r.right = (int) (r.right * scale + 0.5f);
- r.bottom = (int) (r.bottom * scale + 0.5f);
- r.offset(cx, cy);
+ float cx = r.exactCenterX();
+ float cy = r.exactCenterY();
+ r.left = Math.round(cx + (r.left - cx) * scale);
+ r.top = Math.round(cy + (r.top - cy) * scale);
+ r.right = Math.round(cx + (r.right - cx) * scale);
+ r.bottom = Math.round(cy + (r.bottom - cy) * scale);
}
}
@@ -561,6 +560,17 @@
}
/**
+ * Utility method to allow background activity launch for the provided activity options
+ */
+ public static ActivityOptions allowBGLaunch(ActivityOptions options) {
+ if (ATLEAST_U) {
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ }
+ return options;
+ }
+
+ /**
* Returns the full drawable for info without any flattening or pre-processing.
*
* @param shouldThemeIcon If true, will theme icons when applicable
@@ -569,12 +579,13 @@
*/
@TargetApi(Build.VERSION_CODES.TIRAMISU)
public static Drawable getFullDrawable(Context context, ItemInfo info, int width, int height,
- boolean shouldThemeIcon, Object[] outObj) {
+ boolean shouldThemeIcon, Object[] outObj, boolean[] outIsIconThemed) {
Drawable icon = loadFullDrawableWithoutTheme(context, info, width, height, outObj);
if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && shouldThemeIcon) {
AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon.mutate();
Drawable mono = aid.getMonochrome();
if (mono != null && Themes.isThemedIconEnabled(context)) {
+ outIsIconThemed[0] = true;
int[] colors = ThemedIconDrawable.getColors(context);
mono = mono.mutate();
mono.setTint(colors[1]);
@@ -635,7 +646,8 @@
* badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
**/
@TargetApi(Build.VERSION_CODES.O)
- public static Drawable getBadge(Context context, ItemInfo info, Object obj) {
+ public static Drawable getBadge(Context context, ItemInfo info, Object obj,
+ boolean isIconThemed) {
LauncherAppState appState = LauncherAppState.getInstance(context);
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
boolean iconBadged = (info instanceof ItemInfoWithIcon)
@@ -653,7 +665,8 @@
} else {
return Process.myUserHandle().equals(info.user)
? new ColorDrawable(Color.TRANSPARENT)
- : context.getDrawable(R.drawable.ic_work_app_badge);
+ : context.getDrawable(isIconThemed
+ ? R.drawable.ic_work_app_badge_themed : R.drawable.ic_work_app_badge);
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 73bb828..8be8fed 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -67,9 +67,9 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
@@ -562,9 +562,9 @@
// Change the interpolators such that the fade animation plays before the move animation.
// This prevents empty adjacent pages to overlay during animation
mLayoutTransition.setInterpolator(LayoutTransition.DISAPPEARING,
- Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0, 0.5f));
+ Interpolators.clampToProgress(Interpolators.ACCELERATE_DECELERATE, 0, 0.5f));
mLayoutTransition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING,
- Interpolators.clampToProgress(Interpolators.ACCEL_DEACCEL, 0.5f, 1));
+ Interpolators.clampToProgress(Interpolators.ACCELERATE_DECELERATE, 0.5f, 1));
mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
@@ -731,6 +731,14 @@
});
}
+
+ /**
+ * Returns if the given screenId is already in the Workspace
+ */
+ public boolean containsScreenId(int screenId) {
+ return this.mWorkspaceScreens.containsKey(screenId);
+ }
+
/**
* Inserts extra empty pages to the end of the existing workspaces.
* Usually we add one extra empty screen, but when two panel home is enabled we add
@@ -897,9 +905,8 @@
mWorkspaceScreens.remove(emptyScreenId);
mScreenOrder.removeValue(emptyScreenId);
- int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ int newScreenId = LauncherAppState.getInstance(getContext())
+ .getModel().getModelDbController().getNewScreenId();
// Launcher database isn't aware of empty pages that are already bound, so we need to
// skip those IDs manually.
while (mWorkspaceScreens.containsKey(newScreenId)) {
@@ -1840,7 +1847,6 @@
!= LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
boolean willBecomeShortcut =
(info.itemType == ITEM_TYPE_APPLICATION ||
- info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
return (aboveShortcut && willBecomeShortcut);
@@ -2759,7 +2765,7 @@
final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
boolean findNearestVacantCell = true;
- if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
cellLayout, mTargetCell);
float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter(
@@ -2832,8 +2838,7 @@
View view;
switch (info.itemType) {
- case ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
if (info instanceof WorkspaceItemFactory) {
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 4768773..c6c38fc 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -55,6 +55,7 @@
int y = presenterPos.cellY;
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ Log.d(TAG, "add predicted icon " + child.getTag().toString() + " to home screen");
int screenId = presenterPos.screenId;
x = getHotseat().getCellXFromOrder(screenId);
y = getHotseat().getCellYFromOrder(screenId);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 565d7da..e4f34ae 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,6 +18,9 @@
import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE;
+import static com.android.app.animation.Interpolators.ACCELERATE_2;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.ZOOM_OUT;
import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
@@ -30,12 +33,8 @@
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.WORKSPACE_PAGE_INDICATOR;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
-import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
@@ -54,6 +53,7 @@
import com.android.launcher3.LauncherState.PageAlphaProvider;
import com.android.launcher3.LauncherState.PageTranslationProvider;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringAnimationBuilder;
@@ -196,12 +196,12 @@
state.getWorkspaceBackgroundAlpha(mLauncher), LINEAR);
SysUiScrim sysUiScrim = mLauncher.getRootView().getSysUiScrim();
- propertySetter.setFloat(sysUiScrim, SYSUI_PROGRESS,
+ propertySetter.setFloat(sysUiScrim.getSysUIProgress(), AnimatedFloat.VALUE,
state.hasFlag(FLAG_HAS_SYS_UI_SCRIM) ? 1 : 0, LINEAR);
propertySetter.setViewBackgroundColor(mLauncher.getScrimView(),
state.getWorkspaceScrimColor(mLauncher),
- config.getInterpolator(ANIM_SCRIM_FADE, ACCEL_2));
+ config.getInterpolator(ANIM_SCRIM_FADE, ACCELERATE_2));
}
public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 7e943a9..40382b2 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -18,10 +18,12 @@
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
+import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
import android.animation.Animator;
@@ -71,14 +73,12 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
-import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider;
+import com.android.launcher3.allapps.search.AllAppsSearchUiDelegate;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.util.Executors;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -134,12 +134,13 @@
protected final Point mFastScrollerOffset = new Point();
protected final int mScrimColor;
protected final float mHeaderThreshold;
+ protected final AllAppsSearchUiDelegate mSearchUiDelegate;
// Used to animate Search results out and A-Z apps in, or vice-versa.
private final SearchTransitionController mSearchTransitionController;
private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect mInsets = new Rect();
- private final AllAppsStore mAllAppsStore = new AllAppsStore();
+ private final AllAppsStore<T> mAllAppsStore;
private final RecyclerView.OnScrollListener mScrollListener =
new RecyclerView.OnScrollListener() {
@Override
@@ -192,6 +193,7 @@
public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivityContext = ActivityContext.lookupContext(context);
+ mAllAppsStore = new AllAppsStore<>(mActivityContext);
mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mHeaderThreshold = getResources().getDimensionPixelSize(
@@ -206,10 +208,6 @@
mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
AllAppsStore.OnUpdateListener onAppsUpdated = this::onAppsUpdated;
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "ActivityAllAppsContainer#init registeringListener: " +
- onAppsUpdated);
- }
mAllAppsStore.addUpdateListener(onAppsUpdated);
// This is a focus listener that proxies focus from a view into the list view. This is to
@@ -219,11 +217,21 @@
getActiveRecyclerView().requestFocus();
}
});
+ mSearchUiDelegate = createSearchUiDelegate();
initContent();
mSearchTransitionController = new SearchTransitionController(this);
}
+ /** Creates the delegate for initializing search. */
+ protected AllAppsSearchUiDelegate createSearchUiDelegate() {
+ return new AllAppsSearchUiDelegate(this);
+ }
+
+ public AllAppsSearchUiDelegate getSearchUiDelegate() {
+ return mSearchUiDelegate;
+ }
+
/**
* Initializes the view hierarchy and internal variables. Any initialization which actually uses
* these members should be done in {@link #onFinishInflate()}.
@@ -233,7 +241,7 @@
* onFinishInflate -> onPostCreate
*/
protected void initContent() {
- mMainAdapterProvider = createMainAdapterProvider();
+ mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider();
mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN,
new AlphabeticalAppsList<>(mActivityContext, mAllAppsStore, null)));
@@ -250,9 +258,12 @@
mFastScroller = findViewById(R.id.fast_scroller);
mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
- // Add the search box above everything else.
- mSearchContainer = inflateSearchBox();
- addView(mSearchContainer);
+ mSearchContainer = inflateSearchBar();
+ if (!isSearchBarFloating()) {
+ // Add the search box above everything else in this container (if the flag is enabled,
+ // it's added to drag layer in onAttach instead).
+ addView(mSearchContainer);
+ }
mSearchUiManager = (SearchUiManager) mSearchContainer;
}
@@ -283,6 +294,13 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ if (isSearchBarFloating()) {
+ // Note: for Taskbar this is removed in TaskbarAllAppsController#cleanUpOverlay when the
+ // panel is closed. Can't do so in onDetach because we are also a child of drag layer
+ // so can't remove its views during that dispatch.
+ mActivityContext.getDragLayer().addView(mSearchContainer);
+ mSearchUiDelegate.onInitializeSearchBar();
+ }
mActivityContext.addOnDeviceProfileChangeListener(this);
}
@@ -304,7 +322,7 @@
* Temporarily force the bottom sheet to be visible on non-tablets.
*
* @param force {@code true} means bottom sheet will be visible on phones until {@code reset()}.
- **/
+ */
public void forceBottomSheetVisible(boolean force) {
mForceBottomSheetVisible = force;
updateBackgroundVisibility(mActivityContext.getDeviceProfile());
@@ -342,6 +360,7 @@
*/
public void setSearchResults(ArrayList<AdapterItem> results, int searchResultCode) {
setSearchResults(results);
+ mSearchUiDelegate.onSearchResultsChanged(results, searchResultCode);
}
private void animateToSearchState(boolean goingToSearch) {
@@ -374,7 +393,10 @@
rebindAdapters(false);
mRebindAdaptersAfterSearchAnimation = false;
}
- if (!goingToSearch) {
+
+ if (goingToSearch) {
+ mSearchUiDelegate.onAnimateToSearchStateCompleted();
+ } else {
setSearchResults(null);
if (mViewPager != null) {
mViewPager.setCurrentPage(previousPage);
@@ -413,7 +435,7 @@
* A-Z apps list.
*
* @param animate Whether to animate the header during the reset (e.g. switching profile tabs).
- **/
+ */
public void reset(boolean animate) {
reset(animate, true);
}
@@ -423,7 +445,7 @@
*
* @param animate Whether to animate the header during the reset (e.g. switching profile tabs).
* @param exitSearch Whether to force exit the search state and return to A-Z apps list.
- **/
+ */
public void reset(boolean animate, boolean exitSearch) {
for (int i = 0; i < mAH.size(); i++) {
if (mAH.get(i).mRecyclerView != null) {
@@ -441,7 +463,7 @@
updateHeaderScroll(0);
if (exitSearch) {
// Reset the search bar after transitioning home.
- mSearchUiManager.resetSearch();
+ MAIN_EXECUTOR.getHandler().post(mSearchUiManager::resetSearch);
// Animate to A-Z with 0 time to reset the animation with proper state management.
animateToSearchState(false, 0);
}
@@ -486,6 +508,9 @@
// Will be called at the end of the animation.
return;
}
+ if (currentActivePage != SEARCH) {
+ mActivityContext.hideKeyboard();
+ }
if (mAH.get(currentActivePage).mRecyclerView != null) {
mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller);
}
@@ -528,10 +553,14 @@
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
+ final AllAppsRecyclerView mainRecyclerView;
+ final AllAppsRecyclerView workRecyclerView;
if (mUsingTabs) {
- mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
- mAH.get(AdapterHolder.WORK).setup(mViewPager.getChildAt(1), mWorkManager.getMatcher());
- mAH.get(AdapterHolder.WORK).mRecyclerView.setId(R.id.apps_list_view_work);
+ mainRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(0);
+ workRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(1);
+ mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher);
+ mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getMatcher());
+ workRecyclerView.setId(R.id.apps_list_view_work);
if (FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
mWorkManager.newScrollListener());
@@ -543,7 +572,6 @@
mActivityContext.getStatsLogManager().logger()
.log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
}
- mActivityContext.hideKeyboard();
});
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> {
@@ -551,24 +579,30 @@
mActivityContext.getStatsLogManager().logger()
.log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
}
- mActivityContext.hideKeyboard();
});
setDeviceManagementResources();
- onActivePageChanged(mViewPager.getNextPage());
+ if (mHeader.isSetUp()) {
+ onActivePageChanged(mViewPager.getNextPage());
+ }
} else {
- mAH.get(AdapterHolder.MAIN).setup(findViewById(R.id.apps_list_view), null);
+ mainRecyclerView = findViewById(R.id.apps_list_view);
+ workRecyclerView = null;
+ mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, null);
mAH.get(AdapterHolder.WORK).mRecyclerView = null;
}
+ setUpCustomRecyclerViewPool(
+ mainRecyclerView,
+ workRecyclerView,
+ mAllAppsStore.getRecyclerViewPool());
setupHeader();
- if (isSearchBarOnBottom()) {
+ if (isSearchBarFloating()) {
// Keep the scroller above the search bar.
RelativeLayout.LayoutParams scrollerLayoutParams =
(LayoutParams) mFastScroller.getLayoutParams();
- scrollerLayoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps);
- scrollerLayoutParams.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
- scrollerLayoutParams.bottomMargin = getResources().getDimensionPixelSize(
- R.dimen.fastscroll_bottom_margin_floating_search);
+ scrollerLayoutParams.bottomMargin = mSearchContainer.getHeight()
+ + getResources().getDimensionPixelSize(
+ R.dimen.fastscroll_bottom_margin_floating_search);
}
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
@@ -576,6 +610,30 @@
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
}
+ /**
+ * If {@link ENABLE_ALL_APPS_RV_PREINFLATION} is enabled, wire custom
+ * {@link RecyclerView.RecycledViewPool} to main and work {@link AllAppsRecyclerView}.
+ *
+ * Then if {@link ALL_APPS_GONE_VISIBILITY} is enabled, update max pool size. This is because
+ * all apps rv's hidden visibility is changed to {@link View#GONE} from {@link View#INVISIBLE),
+ * thus we cannot rely on layout pass to update pool size.
+ */
+ private static void setUpCustomRecyclerViewPool(
+ @NonNull AllAppsRecyclerView mainRecyclerView,
+ @Nullable AllAppsRecyclerView workRecyclerView,
+ @NonNull RecyclerView.RecycledViewPool recycledViewPool) {
+ if (!ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+ return;
+ }
+ mainRecyclerView.setRecycledViewPool(recycledViewPool);
+ if (workRecyclerView != null) {
+ workRecyclerView.setRecycledViewPool(recycledViewPool);
+ }
+ if (ALL_APPS_GONE_VISIBILITY.get()) {
+ mainRecyclerView.updatePoolSize();
+ }
+ }
+
private void replaceAppsRVContainer(boolean showTabs) {
for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
AdapterHolder adapterHolder = mAH.get(i);
@@ -620,11 +678,9 @@
removeCustomRules(getSearchRecyclerView());
if (!isSearchSupported()) {
layoutWithoutSearchContainer(rvContainer, showTabs);
- } else if (isSearchBarOnBottom()) {
+ } else if (isSearchBarFloating()) {
alignParentTop(rvContainer, showTabs);
alignParentTop(getSearchRecyclerView(), /* tabs= */ false);
- layoutAboveSearchContainer(rvContainer);
- layoutAboveSearchContainer(getSearchRecyclerView());
} else {
layoutBelowSearchContainer(rvContainer, showTabs);
layoutBelowSearchContainer(getSearchRecyclerView(), /* tabs= */ false);
@@ -655,7 +711,7 @@
removeCustomRules(mHeader);
if (!isSearchSupported()) {
layoutWithoutSearchContainer(mHeader, false /* includeTabsMargin */);
- } else if (isSearchBarOnBottom()) {
+ } else if (isSearchBarFloating()) {
alignParentTop(mHeader, false /* includeTabsMargin */);
} else {
layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */);
@@ -695,16 +751,62 @@
}
/**
- * 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(
- ALIGN_PARENT_BOTTOM) == RelativeLayout.TRUE;
+ * @return true if the search bar is floating above this container (at the bottom of the screen)
+ */
+ protected boolean isSearchBarFloating() {
+ return mSearchUiDelegate.isSearchBarFloating();
+ }
+
+ /**
+ * Whether the <em>floating</em> search bar should appear as a small pill when not focused.
+ * <p>
+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
+ * makes sense to use that method to derive an appropriate value for the current/target state.
+ */
+ public boolean shouldFloatingSearchBarBePillWhenUnfocused() {
+ return false;
+ }
+
+ /**
+ * How far from the bottom of the screen the <em>floating</em> search bar should rest when the
+ * IME is not present.
+ * <p>
+ * To hide offscreen, use a negative value.
+ * <p>
+ * Note: if the provided value is non-negative but less than the current bottom insets, the
+ * insets will be applied. As such, you can use 0 to default to this.
+ * <p>
+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
+ * makes sense to use that method to derive an appropriate value for the current/target state.
+ */
+ public int getFloatingSearchBarRestingMarginBottom() {
+ return 0;
+ }
+
+ /**
+ * How far from the start of the screen the <em>floating</em> search bar should rest.
+ * <p>
+ * To use original margin, return a negative value.
+ * <p>
+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
+ * makes sense to use that method to derive an appropriate value for the current/target state.
+ */
+ public int getFloatingSearchBarRestingMarginStart() {
+ DeviceProfile dp = mActivityContext.getDeviceProfile();
+ return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
+ }
+
+ /**
+ * How far from the end of the screen the <em>floating</em> search bar should rest.
+ * <p>
+ * To use original margin, return a negative value.
+ * <p>
+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely
+ * makes sense to use that method to derive an appropriate value for the current/target state.
+ */
+ public int getFloatingSearchBarRestingMarginEnd() {
+ DeviceProfile dp = mActivityContext.getDeviceProfile();
+ return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
}
private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) {
@@ -724,15 +826,6 @@
layoutParams.topMargin = topMargin;
}
- private void layoutAboveSearchContainer(View v) {
- if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
- return;
- }
-
- RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
- layoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps);
- }
-
private void alignParentTop(View v, boolean includeTabsMargin) {
if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
return;
@@ -786,15 +879,10 @@
}
/**
- * Inflates the search box
+ * Inflates the search bar
*/
- protected View inflateSearchBox() {
- return getLayoutInflater().inflate(R.layout.search_container_all_apps, this, false);
- }
-
- /** Creates the adapter provider for the main section. */
- protected SearchAdapterProvider<?> createMainAdapterProvider() {
- return new DefaultSearchAdapterProvider(mActivityContext);
+ protected View inflateSearchBar() {
+ return mSearchUiDelegate.inflateSearchBar();
}
/** The adapter provider for the main section. */
@@ -834,16 +922,7 @@
container.put(R.id.work_tab_state_id, state);
}
- /**
- * Sets the long click listener for icons
- */
- public void setOnIconLongClickListener(OnLongClickListener listener) {
- for (AdapterHolder holder : mAH) {
- holder.mAdapter.setOnIconLongClickListener(listener);
- }
- }
-
- public AllAppsStore getAppsStore() {
+ public AllAppsStore<T> getAppsStore() {
return mAllAppsStore;
}
@@ -855,6 +934,7 @@
public void onDeviceProfileChanged(DeviceProfile dp) {
for (AdapterHolder holder : mAH) {
holder.mAdapter.setAppsPerRow(dp.numShownAllAppsColumns);
+ holder.mAppsList.setNumAppsPerRowAllApps(dp.numShownAllAppsColumns);
if (holder.mRecyclerView != null) {
// Remove all views and clear the pool, while keeping the data same. After this
// call, all the viewHolders will be recreated.
@@ -886,10 +966,6 @@
private void onAppsUpdated() {
mHasWorkApps = Stream.of(mAllAppsStore.getApps()).anyMatch(mWorkManager.getMatcher());
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "ActivityAllAppsContainerView#onAppsUpdated hasWorkApps: " +
- mHasWorkApps + " allApps: " + mAllAppsStore.getApps().length);
- }
if (!isSearching()) {
rebindAdapters();
if (mHasWorkApps) {
@@ -974,7 +1050,7 @@
/**
* The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently
* hidden while searching.
- **/
+ */
public ViewGroup getAppsRecyclerViewContainer() {
return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
}
@@ -999,7 +1075,7 @@
}
public LayoutInflater getLayoutInflater() {
- return LayoutInflater.from(getContext());
+ return mSearchUiDelegate.getLayoutInflater();
}
@Override
@@ -1021,7 +1097,7 @@
setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
} else {
int topPadding = grid.allAppsTopPadding;
- if (isSearchBarOnBottom() && !grid.isTablet) {
+ if (isSearchBarFloating() && !grid.isTablet) {
topPadding += getResources().getDimensionPixelSize(
R.dimen.all_apps_additional_top_padding_floating_search);
}
@@ -1097,7 +1173,10 @@
}
}
- protected boolean shouldShowTabs() {
+ /**
+ * Returns true if the container has work apps.
+ */
+ public boolean shouldShowTabs() {
return mHasWorkApps;
}
@@ -1257,7 +1336,7 @@
final FloatingHeaderView headerView = getFloatingHeaderView();
if (hasBottomSheet) {
// Start adding header protection if search bar or tabs will attach to the top.
- if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) {
+ if (!isSearchBarFloating() || mUsingTabs) {
mTmpRectF.set(
leftWithScale,
topWithScale,
@@ -1313,7 +1392,7 @@
/** Returns the position of the bottom edge of the header */
public int getHeaderBottom() {
int bottom = (int) getTranslationY() + mHeader.getClipTop();
- if (isSearchBarOnBottom()) {
+ if (isSearchBarFloating()) {
if (mActivityContext.getDeviceProfile().isTablet) {
return bottom + mBottomSheetBackground.getTop();
}
@@ -1331,6 +1410,7 @@
protected void onInitializeRecyclerView(RecyclerView rv) {
rv.addOnScrollListener(mScrollListener);
+ mSearchUiDelegate.onInitializeRecyclerView(rv);
}
/** Returns the instance of @{code SearchTransitionController}. */
@@ -1383,6 +1463,9 @@
if (isWork() && mWorkManager.getWorkModeSwitch() != null) {
bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight();
}
+ if (isSearchBarFloating()) {
+ bottomOffset += mSearchContainer.getHeight();
+ }
mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right,
mPadding.bottom + bottomOffset);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 7c5c003..7edbeac 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import static com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN;
@@ -26,6 +27,8 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_COLLAPSE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_EXTEND;
+import static com.android.launcher3.recyclerview.AllAppsRecyclerViewPoolKt.EXTRA_ICONS_COUNT;
+import static com.android.launcher3.recyclerview.AllAppsRecyclerViewPoolKt.PREINFLATE_ICONS_ROW_COUNT;
import static com.android.launcher3.util.LogConfig.SEARCH_LOGGING;
import android.content.Context;
@@ -96,8 +99,18 @@
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
- pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
- * (mNumAppsPerRow + 1));
+
+ // If all apps' hidden visibility is INVISIBLE, we will need to preinflate one page of
+ // all apps icons for smooth scrolling.
+ int maxPoolSizeForAppIcons = (approxRows + 1) * grid.numShownAllAppsColumns;
+ if (ALL_APPS_GONE_VISIBILITY.get()) {
+ // If all apps' hidden visibility is GONE, we need to increase prefinated icons number
+ // by [PREINFLATE_ICONS_ROW_COUNT] rows + [EXTRA_ICONS_COUNT] for fast opening all apps.
+ maxPoolSizeForAppIcons +=
+ PREINFLATE_ICONS_ROW_COUNT * grid.numShownAllAppsColumns + EXTRA_ICONS_COUNT;
+ }
+ pool.setMaxRecycledViews(
+ AllAppsGridAdapter.VIEW_TYPE_ICON, maxPoolSizeForAppIcons);
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 06af970..e724858 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -15,24 +15,27 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
+import android.content.Context;
import android.os.UserHandle;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
import java.util.Arrays;
@@ -45,8 +48,10 @@
/**
* A utility class to maintain the collection of all apps.
+ *
+ * @param <T> The type of the context.
*/
-public class AllAppsStore {
+public class AllAppsStore<T extends Context & ActivityContext> {
// Defer updates flag used to defer all apps updates to the next draw.
public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
@@ -56,7 +61,7 @@
private PackageUserKey mTempKey = new PackageUserKey(null, null);
private AppInfo mTempInfo = new AppInfo();
- private AppInfo[] mApps = EMPTY_ARRAY;
+ private @NonNull AppInfo[] mApps = EMPTY_ARRAY;
private final List<OnUpdateListener> mUpdateListeners = new CopyOnWriteArrayList<>();
private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
@@ -64,20 +69,36 @@
private int mModelFlags;
private int mDeferUpdatesFlags = 0;
private boolean mUpdatePending = false;
+ private final AllAppsRecyclerViewPool mAllAppsRecyclerViewPool = new AllAppsRecyclerViewPool();
+
+ private final T mContext;
public AppInfo[] getApps() {
return mApps;
}
+ public AllAppsStore(@NonNull T context) {
+ mContext = context;
+ }
+
/**
* Sets the current set of apps and sets mapping for {@link PackageUserKey} to Uid for
* the current set of apps.
*/
- public void setApps(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
- mApps = apps;
+ public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
+ mApps = apps == null ? EMPTY_ARRAY : apps;
mModelFlags = flags;
notifyUpdate();
mPackageUserKeytoUidMap = map;
+ // Preinflate all apps RV when apps has changed, which can happen after unlocking screen,
+ // rotating screen, or downloading/upgrading apps.
+ if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+ mAllAppsRecyclerViewPool.preInflateAllAppsViewHolders(mContext);
+ }
+ }
+
+ RecycledViewPool getRecyclerViewPool() {
+ return mAllAppsRecyclerViewPool;
}
/**
@@ -134,9 +155,6 @@
return;
}
for (OnUpdateListener listener : mUpdateListeners) {
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "AllAppsStore#notifyUpdate listener: " + listener);
- }
listener.onAppsUpdated();
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index d4f152a..c09a5b9 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -15,14 +15,15 @@
*/
package com.android.launcher3.allapps;
+import static com.android.app.animation.Interpolators.DECELERATE_1_7;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
@@ -45,6 +46,7 @@
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
@@ -53,7 +55,6 @@
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;
@@ -234,7 +235,11 @@
*/
public void setProgress(float progress) {
mProgress = progress;
- getAppsViewProgressTranslationY().setValue(mProgress * mShiftRange);
+ boolean fromBackground =
+ mLauncher.getStateManager().getCurrentStableState() == BACKGROUND_APP;
+ // Allow apps panel to shift the full screen if coming from another app.
+ float shiftRange = fromBackground ? mLauncher.getDeviceProfile().heightPx : mShiftRange;
+ getAppsViewProgressTranslationY().setValue(mProgress * shiftRange);
mLauncher.onAllAppsTransition(1 - progress);
boolean hasScrim = progress < NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD
@@ -380,7 +385,7 @@
// need to decide depending on the release velocity
Interpolator verticalProgressInterpolator = config.getInterpolator(ANIM_VERTICAL_PROGRESS,
- config.userControlled ? LINEAR : DEACCEL_1_7);
+ config.userControlled ? LINEAR : DECELERATE_1_7);
Animator anim = createSpringAnimation(mProgress, targetProgress);
anim.setInterpolator(verticalProgressInterpolator);
anim.addListener(getProgressAnimatorListener());
@@ -433,7 +438,8 @@
mAppsView = appsView;
mAppsView.setScrimView(scrimView);
- mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT);
+ mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT,
+ FeatureFlags.ALL_APPS_GONE_VISIBILITY.get() ? View.GONE : View.INVISIBLE);
mAppsViewAlpha.setUpdateVisibility(true);
mAppsViewTranslationY = new MultiPropertyFactory<>(
mAppsView, VIEW_TRANSLATE_Y, APPS_VIEW_INDEX_COUNT, Float::sum);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionListener.java b/src/com/android/launcher3/allapps/AllAppsTransitionListener.java
new file mode 100644
index 0000000..4a17e29
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionListener.java
@@ -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.allapps;
+
+/**
+ * An interface for listening to all-apps open-close transition
+ */
+public interface AllAppsTransitionListener {
+ /**
+ * Called when the transition starts
+ * @param toAllApps {@code true} if this transition is supposed to end in the AppApps UI
+ *
+ * @see ActivityAllAppsContainerView
+ */
+ void onAllAppsTransitionStart(boolean toAllApps);
+
+ /**
+ * Called when the transition ends
+ * @param toAllApps {@code true} if the final state is all-apps
+ *
+ * @see ActivityAllAppsContainerView
+ */
+ void onAllAppsTransitionEnd(boolean toAllApps);
+}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 29767bf..ee4b5bc 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -69,7 +69,7 @@
// The set of apps from the system
private final List<AppInfo> mApps = new ArrayList<>();
@Nullable
- private final AllAppsStore mAllAppsStore;
+ private final AllAppsStore<T> mAllAppsStore;
// The number of results in current adapter
private int mAccessibilityResultsCount = 0;
@@ -82,22 +82,27 @@
private final ArrayList<AdapterItem> mSearchResults = new ArrayList<>();
private BaseAllAppsAdapter<T> mAdapter;
private AppInfoComparator mAppNameComparator;
- private final int mNumAppsPerRowAllApps;
+ private int mNumAppsPerRowAllApps;
private int mNumAppRowsInAdapter;
private Predicate<ItemInfo> mItemFilter;
- public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore,
+ public AlphabeticalAppsList(Context context, @Nullable AllAppsStore<T> appsStore,
WorkProfileManager workProfileManager) {
mAllAppsStore = appsStore;
mActivityContext = ActivityContext.lookupContext(context);
mAppNameComparator = new AppInfoComparator(context);
mWorkProviderManager = workProfileManager;
- mNumAppsPerRowAllApps = mActivityContext.getDeviceProfile().inv.numAllAppsColumns;
+ mNumAppsPerRowAllApps = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
if (mAllAppsStore != null) {
mAllAppsStore.addUpdateListener(this);
}
}
+ /** Set the number of apps per row when device profile changes. */
+ public void setNumAppsPerRowAllApps(int numAppsPerRow) {
+ mNumAppsPerRowAllApps = numAppsPerRow;
+ }
+
public void updateItemFilter(Predicate<ItemInfo> itemFilter) {
this.mItemFilter = itemFilter;
onAppsUpdated();
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 8fa4276..be0a898 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -15,10 +15,7 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
-
import android.content.Context;
-import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -27,13 +24,12 @@
import android.view.ViewGroup;
import android.widget.TextView;
-import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.views.ActivityContext;
@@ -139,31 +135,24 @@
protected final LayoutInflater mLayoutInflater;
protected final OnClickListener mOnIconClickListener;
- protected OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
+ protected final OnLongClickListener mOnIconLongClickListener;
protected OnFocusChangeListener mIconFocusListener;
private final int mExtraTextHeight;
public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
- Resources res = activityContext.getResources();
mActivityContext = activityContext;
mApps = apps;
mLayoutInflater = inflater;
mOnIconClickListener = mActivityContext.getItemOnClickListener();
+ mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
mAdapterProvider = adapterProvider;
mExtraTextHeight = Utilities.calculateTextHeight(
mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
}
- /**
- * Sets the long click listener for icons
- */
- public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
- mOnIconLongClickListener = listener;
- }
-
/** Checks if the passed viewType represents all apps divider. */
public static boolean isDividerViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index aefedae..5e48177 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -22,6 +22,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StateManager;
/**
* AllAppsContainerView with launcher specific callbacks
@@ -53,4 +54,87 @@
public boolean isInAllApps() {
return mActivityContext.getStateManager().isInStableState(LauncherState.ALL_APPS);
}
+
+ @Override
+ public boolean shouldFloatingSearchBarBePillWhenUnfocused() {
+ if (!isSearchBarFloating()) {
+ return false;
+ }
+ Launcher launcher = mActivityContext;
+ StateManager<LauncherState> manager = launcher.getStateManager();
+ if (manager.isInTransition() && manager.getTargetState() != null) {
+ return manager.getTargetState().shouldFloatingSearchBarUsePillWhenUnfocused(launcher);
+ }
+ return manager.getCurrentStableState()
+ .shouldFloatingSearchBarUsePillWhenUnfocused(launcher);
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginBottom() {
+ if (!isSearchBarFloating()) {
+ return super.getFloatingSearchBarRestingMarginBottom();
+ }
+ Launcher launcher = mActivityContext;
+ StateManager<LauncherState> stateManager = launcher.getStateManager();
+
+ // We want to rest at the current state's resting position, unless we are in transition and
+ // the target state's resting position is higher (that way if we are closing the keyboard,
+ // we can stop translating at that point).
+ int currentStateMarginBottom = stateManager.getCurrentStableState()
+ .getFloatingSearchBarRestingMarginBottom(launcher);
+ int targetStateMarginBottom = -1;
+ if (stateManager.isInTransition() && stateManager.getTargetState() != null) {
+ targetStateMarginBottom = stateManager.getTargetState()
+ .getFloatingSearchBarRestingMarginBottom(launcher);
+ if (targetStateMarginBottom < 0) {
+ // Go ahead and move offscreen.
+ return targetStateMarginBottom;
+ }
+ }
+ return Math.max(targetStateMarginBottom, currentStateMarginBottom);
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginStart() {
+ if (!isSearchBarFloating()) {
+ return super.getFloatingSearchBarRestingMarginStart();
+ }
+
+ StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+
+ // Special case to not expand the search bar when exiting All Apps on phones.
+ if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
+ && mActivityContext.getDeviceProfile().isPhone) {
+ return LauncherState.ALL_APPS.getFloatingSearchBarRestingMarginStart(mActivityContext);
+ }
+
+ if (stateManager.isInTransition() && stateManager.getTargetState() != null) {
+ return stateManager.getTargetState()
+ .getFloatingSearchBarRestingMarginStart(mActivityContext);
+ }
+ return stateManager.getCurrentStableState()
+ .getFloatingSearchBarRestingMarginStart(mActivityContext);
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginEnd() {
+ if (!isSearchBarFloating()) {
+ return super.getFloatingSearchBarRestingMarginEnd();
+ }
+
+ StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+
+ // Special case to not expand the search bar when exiting All Apps on phones.
+ if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
+ && mActivityContext.getDeviceProfile().isPhone) {
+ return LauncherState.ALL_APPS.getFloatingSearchBarRestingMarginEnd(mActivityContext);
+ }
+
+ if (stateManager.isInTransition() && stateManager.getTargetState() != null) {
+ return stateManager.getTargetState()
+ .getFloatingSearchBarRestingMarginEnd(mActivityContext);
+ }
+ return stateManager.getCurrentStableState()
+ .getFloatingSearchBarRestingMarginEnd(mActivityContext);
+ }
}
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index b01ea53..eb1bc0a 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -20,12 +20,12 @@
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
+import static com.android.app.animation.Interpolators.DECELERATE_1_7;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
@@ -48,7 +48,7 @@
private static final String LOG_TAG = "SearchTransitionCtrl";
// Interpolator when the user taps the QSB while already in All Apps.
- private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
+ private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DECELERATE_1_7;
// Interpolator when the user taps the QSB from home screen, so transition to all apps is
// happening simultaneously.
private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT;
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 2174936..bfd8967 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -49,6 +49,14 @@
ExtendedEditText getEditText();
/**
+ * Hint to the edit text that it is about to be focused or unfocused. This can be used to start
+ * animating the edit box accordingly, e.g. after a gesture completes.
+ *
+ * @param focused true if the edit text is about to be focused, false if it will be unfocused
+ */
+ default void prepareToFocusEditText(boolean focused) {}
+
+ /**
* Sets whether EditText background should be visible
* @param maxAlpha defines the maximum alpha the background should animates to
*/
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index f84876b..144381c 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth;
-import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -36,7 +35,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.views.ActivityContext;
@@ -93,8 +91,6 @@
setInsets(mActivityContext.getDeviceProfile().getInsets());
updateStringFromCache();
-
- getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
@Override
@@ -105,7 +101,7 @@
if (lp != null) {
int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom);
DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
- if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
+ if (mActivityContext.getAppsView().isSearchBarFloating()) {
bottomMargin += dp.hotseatQsbHeight;
}
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 44c233f..1ac8d87 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -25,7 +25,6 @@
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.os.Build;
@@ -47,7 +46,6 @@
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
import java.lang.annotation.Retention;
@@ -144,10 +142,6 @@
}
private void updateCurrentState(@WorkProfileState int currentState) {
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "WorkProfileManager#updateCurrentState: " +
- currentState, new Throwable());
- }
mCurrentState = currentState;
if (getAH() != null) {
getAH().mAppsList.updateAdapterItems();
@@ -166,10 +160,6 @@
* Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView}
*/
public boolean attachWorkModeSwitch() {
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "ActivityAllAppsContainerView#attachWorkModeSwitch "
- + "mWorkModeSwitch: " + mWorkModeSwitch);
- }
if (!mAllApps.getAppsStore().hasModelFlag(
FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) {
Log.e(TAG, "unable to attach work mode switch; Missing required permissions");
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4427a49..ecbc7a9 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -160,7 +160,7 @@
* Focuses the search field to handle key events.
*/
public void focusSearchField() {
- mInput.showKeyboard();
+ mInput.showKeyboard(true /* shouldFocus */);
}
/**
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchUiDelegate.java b/src/com/android/launcher3/allapps/search/AllAppsSearchUiDelegate.java
new file mode 100644
index 0000000..2347bfd
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchUiDelegate.java
@@ -0,0 +1,86 @@
+/*
+ * 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.allapps.search;
+
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.List;
+
+/** Initializes the search box and its interactions with All Apps. */
+public class AllAppsSearchUiDelegate {
+
+ protected final ActivityAllAppsContainerView<?> mAppsView;
+ protected final ActivityContext mActivityContext;
+
+ public AllAppsSearchUiDelegate(ActivityAllAppsContainerView<?> appsView) {
+ mAppsView = appsView;
+ mActivityContext = ActivityContext.lookupContext(mAppsView.getContext());
+ }
+
+ /** Invoked when an All Apps {@link RecyclerView} is initialized. */
+ public void onInitializeRecyclerView(RecyclerView rv) {
+ // Do nothing.
+ }
+
+ /** Invoked when search results are updated in All Apps. */
+ public void onSearchResultsChanged(List<AdapterItem> results, int searchResultCode) {
+ // Do nothing.
+ }
+
+ /** Invoked when transition animations to go to search is completed . */
+ public void onAnimateToSearchStateCompleted() {
+ // Do nothing
+ }
+
+ /** Invoked when the search bar has been added to All Apps. */
+ public void onInitializeSearchBar() {
+ // Do nothing.
+ }
+
+ /** Invoked when the search bar has been removed from All Apps. */
+ public void onDestroySearchBar() {
+ // Do nothing.
+ }
+
+ /** The layout inflater for All Apps and search UI. */
+ public LayoutInflater getLayoutInflater() {
+ return LayoutInflater.from(mAppsView.getContext());
+ }
+
+ /** Inflate the search bar for All Apps. */
+ public View inflateSearchBar() {
+ return getLayoutInflater().inflate(R.layout.search_container_all_apps, mAppsView, false);
+ }
+
+ /** Whether the search box is floating above the apps surface (inset by the IME). */
+ public boolean isSearchBarFloating() {
+ return false;
+ }
+
+ /** Creates the adapter provider for the main section. */
+ public SearchAdapterProvider<?> createMainAdapterProvider() {
+ return new DefaultSearchAdapterProvider(mActivityContext);
+ }
+}
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
index 8dad1b4..4382174 100644
--- a/src/com/android/launcher3/anim/AlphaUpdateListener.java
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -53,8 +53,18 @@
}
public static void updateVisibility(View view) {
- if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != View.INVISIBLE) {
- view.setVisibility(View.INVISIBLE);
+ updateVisibility(view, View.INVISIBLE);
+ }
+
+ /**
+ * Update view's visibility.
+ *
+ * @param view View that needs to update visibility.
+ * @param hiddenVisibility {@link View#GONE} or {@link View#INVISIBLE}
+ */
+ public static void updateVisibility(View view, int hiddenVisibility) {
+ if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != hiddenVisibility) {
+ view.setVisibility(hiddenVisibility);
} else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
&& view.getVisibility() != View.VISIBLE) {
if (view instanceof ViewGroup) {
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 1cc0c21..d11a51f 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -15,10 +15,10 @@
*/
package com.android.launcher3.anim;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.clampToProgress;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.Utilities.boundToRange;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import android.animation.Animator;
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
deleted file mode 100644
index e886543..0000000
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.anim;
-
-import android.graphics.Path;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.view.animation.PathInterpolator;
-
-import com.android.launcher3.Utilities;
-
-/**
- * Common interpolators used in Launcher
- */
-public class Interpolators {
-
- public static final Interpolator LINEAR = new LinearInterpolator();
-
- public static final Interpolator ACCEL = new AccelerateInterpolator();
- public static final Interpolator ACCEL_0_5 = new AccelerateInterpolator(0.5f);
- public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f);
- public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f);
- public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2);
-
- public static final Interpolator DEACCEL = new DecelerateInterpolator();
- public static final Interpolator DEACCEL_1_5 = new DecelerateInterpolator(1.5f);
- public static final Interpolator DEACCEL_1_7 = new DecelerateInterpolator(1.7f);
- public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
- public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
- public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
-
- public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
-
- public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-
- public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f);
- public static final Interpolator AGGRESSIVE_EASE_IN_OUT = new PathInterpolator(0.6f,0, 0.4f, 1);
-
- public static final Interpolator DECELERATED_EASE = new PathInterpolator(0, 0, .2f, 1f);
- public static final Interpolator ACCELERATED_EASE = new PathInterpolator(0.4f, 0, 1f, 1f);
- public static final Interpolator PREDICTIVE_BACK_DECELERATED_EASE =
- new PathInterpolator(0, 0, 0, 1f);
-
- /**
- * The default emphasized interpolator. Used for hero / emphasized movement of content.
- */
- public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
- public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
- 0.3f, 0f, 0.8f, 0.15f);
- public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
- 0.05f, 0.7f, 0.1f, 1f);
-
- public static final Interpolator EXAGGERATED_EASE;
-
- public static final Interpolator INSTANT = t -> 1;
- /**
- * All values of t map to 0 until t == 1. This is primarily useful for setting view visibility,
- * which should only happen at the very end of the animation (when it's already hidden).
- */
- public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1;
-
- static {
- Path exaggeratedEase = new Path();
- exaggeratedEase.moveTo(0, 0);
- exaggeratedEase.cubicTo(0.05f, 0f, 0.133333f, 0.08f, 0.166666f, 0.4f);
- exaggeratedEase.cubicTo(0.225f, 0.94f, 0.5f, 1f, 1f, 1f);
- EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase);
- }
-
- public static final Interpolator OVERSHOOT_0_75 = new OvershootInterpolator(0.75f);
- public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f);
- public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f);
-
- public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
- new PathInterpolator(0.3f, 0f, 0.1f, 1f);
- public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL =
- v -> ACCEL_DEACCEL.getInterpolation(TOUCH_RESPONSE_INTERPOLATOR.getInterpolation(v));
-
- /**
- * Inversion of ZOOM_OUT, compounded with an ease-out.
- */
- public static final Interpolator ZOOM_IN = new Interpolator() {
- @Override
- public float getInterpolation(float v) {
- return DEACCEL_3.getInterpolation(1 - ZOOM_OUT.getInterpolation(1 - v));
- }
- };
-
- public static final Interpolator ZOOM_OUT = new Interpolator() {
-
- private static final float FOCAL_LENGTH = 0.35f;
-
- @Override
- public float getInterpolation(float v) {
- return zInterpolate(v);
- }
-
- /**
- * This interpolator emulates the rate at which the perceived scale of an object changes
- * as its distance from a camera increases. When this interpolator is applied to a scale
- * animation on a view, it evokes the sense that the object is shrinking due to moving away
- * from the camera.
- */
- private float zInterpolate(float input) {
- return (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + input)) /
- (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + 1.0f));
- }
- };
-
- public static final Interpolator SCROLL = new Interpolator() {
- @Override
- public float getInterpolation(float t) {
- t -= 1.0f;
- return t*t*t*t*t + 1;
- }
- };
-
- public static final Interpolator SCROLL_CUBIC = new Interpolator() {
- @Override
- public float getInterpolation(float t) {
- t -= 1.0f;
- return t*t*t + 1;
- }
- };
-
- private static final float FAST_FLING_PX_MS = 10;
-
- public static Interpolator scrollInterpolatorForVelocity(float velocity) {
- return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC;
- }
-
- /**
- * Create an OvershootInterpolator with tension directly related to the velocity (in px/ms).
- * @param velocity The start velocity of the animation we want to overshoot.
- */
- public static Interpolator overshootInterpolatorForVelocity(float velocity) {
- return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f));
- }
-
- /**
- * Returns a function that runs the given interpolator such that the entire progress is set
- * between the given bounds. That is, we set the interpolation to 0 until lowerBound and reach
- * 1 by upperBound.
- */
- public static Interpolator clampToProgress(Interpolator interpolator, float lowerBound,
- float upperBound) {
- if (upperBound < lowerBound) {
- throw new IllegalArgumentException(
- String.format("upperBound (%f) must be greater than lowerBound (%f)",
- upperBound, lowerBound));
- }
- return t -> clampToProgress(interpolator, t, lowerBound, upperBound);
- }
-
- /**
- * Returns the progress value's progress between the lower and upper bounds. That is, the
- * progress will be 0f from 0f to lowerBound, and reach 1f by upperBound.
- *
- * Between lowerBound and upperBound, the progress value will be interpolated using the provided
- * interpolator.
- */
- public static float clampToProgress(
- Interpolator interpolator, float progress, float lowerBound, float upperBound) {
- if (upperBound < lowerBound) {
- throw new IllegalArgumentException(
- String.format("upperBound (%f) must be greater than lowerBound (%f)",
- upperBound, lowerBound));
- }
-
- if (progress == lowerBound && progress == upperBound) {
- return progress == 0f ? 0 : 1;
- }
- if (progress < lowerBound) {
- return 0;
- }
- if (progress > upperBound) {
- return 1;
- }
- return interpolator.getInterpolation((progress - lowerBound) / (upperBound - lowerBound));
- }
-
- /**
- * Returns the progress value's progress between the lower and upper bounds. That is, the
- * progress will be 0f from 0f to lowerBound, and reach 1f by upperBound.
- */
- public static float clampToProgress(float progress, float lowerBound, float upperBound) {
- return clampToProgress(Interpolators.LINEAR, progress, lowerBound, upperBound);
- }
-
- /**
- * Runs the given interpolator such that the interpolated value is mapped to the given range.
- * This is useful, for example, if we only use this interpolator for part of the animation,
- * such as to take over a user-controlled animation when they let go.
- */
- public static Interpolator mapToProgress(Interpolator interpolator, float lowerBound,
- float upperBound) {
- return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound);
- }
-
- /**
- * Returns the reverse of the provided interpolator, following the formula: g(x) = 1 - f(1 - x).
- * In practice, this means that if f is an interpolator used to model a value animating between
- * m and n, g is the interpolator to use to obtain the specular behavior when animating from n
- * to m.
- */
- public static Interpolator reverse(Interpolator interpolator) {
- return t -> 1 - interpolator.getInterpolation(1 - t);
- }
-
- // Create the default emphasized interpolator
- private static PathInterpolator createEmphasizedInterpolator() {
- Path path = new Path();
- // Doing the same as fast_out_extra_slow_in
- path.moveTo(0f, 0f);
- path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
- path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
- return new PathInterpolator(path);
- }
-}
diff --git a/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java b/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java
index b911928..39386fa 100644
--- a/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java
+++ b/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java
@@ -44,14 +44,30 @@
private float mInitialTranslation;
private float mTerminalTranslation;
+ private KeyboardTranslationState mKeyboardTranslationState = KeyboardTranslationState.SYSTEM;
+
+ /** Current state of the keyboard. */
+ public enum KeyboardTranslationState {
+ // We are not controlling the keyboard, and it may or may not be translating.
+ SYSTEM,
+ // We are about to gain control of the keyboard, but the current state may be transient.
+ MANUAL_PREPARED,
+ // We are manually translating the keyboard.
+ MANUAL_ONGOING
+ }
public KeyboardInsetAnimationCallback(View view) {
super(DISPATCH_MODE_STOP);
mView = view;
}
+ public KeyboardTranslationState getKeyboardTranslationState() {
+ return mKeyboardTranslationState;
+ }
+
@Override
public void onPrepare(WindowInsetsAnimation animation) {
+ mKeyboardTranslationState = KeyboardTranslationState.MANUAL_PREPARED;
mInitialTranslation = mView.getTranslationY();
}
@@ -62,6 +78,7 @@
mTerminalTranslation = mView.getTranslationY();
// Reset the translation in case the view is drawn before onProgress gets called.
mView.setTranslationY(mInitialTranslation);
+ mKeyboardTranslationState = KeyboardTranslationState.MANUAL_ONGOING;
if (mView instanceof KeyboardInsetListener) {
((KeyboardInsetListener) mView).onTranslationStart();
}
@@ -90,6 +107,10 @@
mView.setTranslationY(translationY);
}
+ if (mView instanceof KeyboardInsetListener) {
+ ((KeyboardInsetListener) mView).onKeyboardAlphaChanged(animation.getAlpha());
+ }
+
return windowInsets;
}
@@ -98,7 +119,7 @@
if (mView instanceof KeyboardInsetListener) {
((KeyboardInsetListener) mView).onTranslationEnd();
}
- super.onEnd(animation);
+ mKeyboardTranslationState = KeyboardTranslationState.SYSTEM;
}
/**
@@ -111,6 +132,13 @@
void onTranslationStart();
/**
+ * Called from {@link KeyboardInsetAnimationCallback#onProgress}
+ *
+ * @param alpha the current IME alpha
+ */
+ default void onKeyboardAlphaChanged(float alpha) {}
+
+ /**
* Called from {@link KeyboardInsetAnimationCallback#onEnd}
*/
void onTranslationEnd();
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index 40fa0cf..bc7b7f0 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.anim;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
import android.animation.Animator;
import android.animation.ValueAnimator;
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
new file mode 100644
index 0000000..1dc4ad2
--- /dev/null
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -0,0 +1,102 @@
+/*
+ * 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.apppairs;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * A {@link android.widget.FrameLayout} used to represent an app pair icon on the workspace.
+ */
+public class AppPairIcon extends FrameLayout implements DraggableView {
+
+ private ActivityContext mActivity;
+ private BubbleTextView mAppPairName;
+ private FolderInfo mInfo;
+
+ public AppPairIcon(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AppPairIcon(Context context) {
+ super(context);
+ }
+
+ /**
+ * Builds an AppPairIcon to be added to the Launcher
+ */
+ public static AppPairIcon inflateIcon(int resId, ActivityContext activity,
+ @Nullable ViewGroup group, FolderInfo appPairInfo) {
+
+ LayoutInflater inflater = (group != null)
+ ? LayoutInflater.from(group.getContext())
+ : activity.getLayoutInflater();
+ AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false);
+
+ // Sort contents, so that left-hand app comes first
+ Collections.sort(appPairInfo.contents, Comparator.comparingInt(a -> a.rank));
+
+ icon.setClipToPadding(false);
+ icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name);
+
+ // TODO (jeremysim b/274189428): Replace this placeholder icon
+ WorkspaceItemInfo placeholder = new WorkspaceItemInfo();
+ placeholder.newIcon(icon.getContext());
+ icon.mAppPairName.applyFromWorkspaceItem(placeholder);
+
+ icon.mAppPairName.setText(appPairInfo.title);
+
+ icon.setTag(appPairInfo);
+ icon.setOnClickListener(activity.getItemOnClickListener());
+ icon.mInfo = appPairInfo;
+ icon.mActivity = activity;
+
+ icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
+
+ return icon;
+ }
+
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_ICON;
+ }
+
+ @Override
+ public void getWorkspaceVisualDragBounds(Rect bounds) {
+ mAppPairName.getIconBounds(bounds);
+ }
+
+ public FolderInfo getInfo() {
+ return mInfo;
+ }
+}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e08c671..77bd898 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -82,15 +82,12 @@
* <p>
*/
// TODO(Block 1): Clean up flags
- public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = getReleaseFlag(270394223,
- "ENABLE_ONE_SEARCH_MOTION", ENABLED, "Enables animations in OneSearch.");
-
public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = getReleaseFlag(
- 270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", DISABLED,
+ 270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", ENABLED,
"Enable option to replace decorator-based search result backgrounds with drawables");
public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = getReleaseFlag(
- 270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", DISABLED,
+ 270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", ENABLED,
"Enable option to launch search results using the new view container transitions");
// TODO(Block 2): Clean up flags
@@ -119,7 +116,8 @@
// TODO(Block 4): Cleanup flags
public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR =
getReleaseFlag(268388460, "ENABLE_FLOATING_SEARCH_BAR", DISABLED,
- "Keep All Apps search bar at the bottom (but above keyboard if open)");
+ "Allow search bar to persist and animate across states, and attach to"
+ + " the keyboard from the bottom of the screen");
public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW =
getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
@@ -131,7 +129,7 @@
// TODO(Block 5): Clean up flags
public static final BooleanFlag ENABLE_TWOLINE_DEVICESEARCH = getDebugFlag(201388851,
- "ENABLE_TWOLINE_DEVICESEARCH", TEAMFOOD,
+ "ENABLE_TWOLINE_DEVICESEARCH", ENABLED,
"Enable two line label for icons with labels on device search.");
public static final BooleanFlag ENABLE_ICON_IN_TEXT_HEADER = getDebugFlag(270395143,
@@ -185,17 +183,13 @@
"Enables predictive back animation from all apps and widgets to home");
// TODO(Block 11): Clean up flags
- public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(270392643,
- "ENABLE_TWO_PANEL_HOME", ENABLED,
- "Uses two panel on home screen. Only applicable on large screen devices.");
-
- public static final BooleanFlag FOLDABLE_WORKSPACE_REORDER = getDebugFlag(270395070,
- "FOLDABLE_WORKSPACE_REORDER", DISABLED,
- "In foldables, when reordering the icons and widgets, is now going to use both sides");
-
public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(270395274,
"FOLDABLE_SINGLE_PAGE", DISABLED, "Use a single page for the workspace");
+ public static final BooleanFlag ENABLE_PARAMETRIZE_REORDER = getDebugFlag(289420844,
+ "ENABLE_PARAMETRIZE_REORDER", DISABLED,
+ "Enables generating the reorder using a set of parameters");
+
// TODO(Block 12): Clean up flags
public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
"ENABLE_MULTI_INSTANCE", DISABLED,
@@ -236,7 +230,7 @@
"COLLECT_SEARCH_HISTORY", DISABLED, "Allow launcher to collect search history for log");
public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
- "ENABLE_TWOLINE_ALLAPPS", TEAMFOOD, "Enables two line label inside all apps.");
+ "ENABLE_TWOLINE_ALLAPPS", DISABLED, "Enables two line label inside all apps.");
public static final BooleanFlag IME_STICKY_SNACKBAR_EDU = getDebugFlag(270391693,
"IME_STICKY_SNACKBAR_EDU", ENABLED, "Show sticky IME edu in AllApps");
@@ -253,6 +247,10 @@
"INJECT_FALLBACK_APP_CORPUS_RESULTS", DISABLED,
"Inject fallback app corpus result when AiAi fails to return it.");
+ public static final BooleanFlag ENABLE_LONG_PRESS_NAV_HANDLE =
+ getReleaseFlag(282993230, "ENABLE_LONG_PRESS_NAV_HANDLE", TEAMFOOD,
+ "Enables long pressing on the bottom bar nav handle to trigger events.");
+
// TODO(Block 17): Clean up flags
public static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
"ENABLE_TASKBAR_PINNING", DISABLED,
@@ -260,11 +258,6 @@
+ "taskbar flavors");
// TODO(Block 18): Clean up flags
- public static final BooleanFlag ENABLE_LAUNCH_FROM_STAGED_APP = getDebugFlag(270395567,
- "ENABLE_LAUNCH_FROM_STAGED_APP", ENABLED,
- "Enable the ability to tap a staged app during split select to launch it in full "
- + "screen");
-
public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
"ENABLE_APP_PAIRS", DISABLED,
"Enables the ability to create and save app pairs on the Home screen for easy"
@@ -287,16 +280,15 @@
"ENABLE_BACK_SWIPE_HOME_ANIMATION", ENABLED,
"Enables home animation to icon when user swipes back.");
+ public static final BooleanFlag ENABLE_DYNAMIC_TASKBAR_THRESHOLDS = getDebugFlag(294252473,
+ "ENABLE_DYNAMIC_TASKBAR_THRESHOLDS", ENABLED,
+ "Enables taskbar thresholds that scale based on screen size.");
+
// TODO(Block 21): Clean up flags
public static final BooleanFlag ENABLE_APP_ICON_FOR_INLINE_SHORTCUTS = getDebugFlag(270395087,
"ENABLE_APP_ICON_IN_INLINE_SHORTCUTS", DISABLED, "Show app icon for inline shortcut");
// TODO(Block 22): Clean up flags
- public static final BooleanFlag RECEIVE_UNFOLD_EVENTS_FROM_SYSUI = getDebugFlag(270397209,
- "RECEIVE_UNFOLD_EVENTS_FROM_SYSUI", ENABLED,
- "Enables receiving unfold animation events from sysui instead of calculating "
- + "them in launcher process using hinge sensor values.");
-
public static final BooleanFlag ENABLE_WIDGET_TRANSITION_FOR_RESIZING = getDebugFlag(268553314,
"ENABLE_WIDGET_TRANSITION_FOR_RESIZING", DISABLED,
"Enable widget transition animation when resizing the widgets");
@@ -309,11 +301,11 @@
// TODO(Block 23): Clean up flags
public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
- "ENABLE_GRID_ONLY_OVERVIEW", DISABLED,
+ "ENABLE_GRID_ONLY_OVERVIEW", TEAMFOOD,
"Enable a grid-only overview without a focused task.");
public static final BooleanFlag ENABLE_CURSOR_HOVER_STATES = getDebugFlag(243191650,
- "ENABLE_CURSOR_HOVER_STATES", DISABLED,
+ "ENABLE_CURSOR_HOVER_STATES", TEAMFOOD,
"Enables cursor hover states for certain elements.");
// TODO(Block 24): Clean up flags
@@ -368,10 +360,6 @@
"Use inbuilt monochrome icons if app doesn't provide one");
// TODO(Block 28): Clean up flags
- public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE = getDebugFlag(270393906,
- "ENABLE_SPLIT_FROM_WORKSPACE", ENABLED,
- "Enable initiating split screen from workspace.");
-
public static final BooleanFlag ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS =
getDebugFlag(270394122, "ENABLE_SPLIT_FROM_FULLSCREEN_SHORTCUT", DISABLED,
"Enable splitting from fullscreen app with keyboard shortcuts");
@@ -380,8 +368,12 @@
270393453, "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", DISABLED,
"Enable initiating split screen from workspace to workspace.");
+ public static final BooleanFlag ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE = getDebugFlag(
+ 279586624, "ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE", DISABLED,
+ "Enable initiating split screen from desktop mode to workspace.");
+
public static final BooleanFlag ENABLE_TRACKPAD_GESTURE = getDebugFlag(271010401,
- "ENABLE_TRACKPAD_GESTURE", DISABLED, "Enables trackpad gesture.");
+ "ENABLE_TRACKPAD_GESTURE", ENABLED, "Enables trackpad gesture.");
// TODO(Block 29): Clean up flags
public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(270393897,
@@ -400,17 +392,25 @@
"USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED,
"Use local overrides for search request timeout");
- // TODO(Block 31)
- public static final BooleanFlag ENABLE_SPLIT_LAUNCH_DATA_REFACTOR = getDebugFlag(279494325,
- "ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", ENABLED,
- "Use refactored split launching code path");
+ // TODO(Block 31): Clean up flags
- // TODO(Block 32): Empty block
-
+ // TODO(Block 32): Clean up flags
public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436,
"ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
"Enables new workspace grid calculations method.");
+ // TODO(Block 33): Clean up flags
+ public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
+ "ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED,
+ "Enables preinflating all apps icons to avoid scrolling jank.");
+
+ // TODO(Block 34): Clean up flags
+ public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514,
+ "ALL_APPS_GONE_VISIBILITY", ENABLED,
+ "Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
+
+ // TODO(Block 35): Empty block
+
public static class BooleanFlag {
private final boolean mCurrentValue;
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 00f4285..213c458 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
import android.annotation.TargetApi;
-import android.app.ActivityOptions;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ClipData;
@@ -68,6 +67,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.views.AbstractSlideInView;
@@ -259,9 +259,7 @@
.setPackage(getPackageName())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Launcher.ACTIVITY_TRACKER.registerCallback(listener);
- startActivity(homeIntent,
- ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out)
- .toBundle());
+ startActivity(homeIntent, ApiWrapper.createFadeOutAnimOptions(this).toBundle());
logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED);
mFinishOnPause = true;
return false;
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 9867268..0d51d48 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -28,9 +28,9 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 366870b..f18f900 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -19,11 +19,11 @@
import static android.animation.ObjectAnimator.ofFloat;
+import static com.android.app.animation.Interpolators.DECELERATE_1_5;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.Utilities.mapRange;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import android.animation.Animator;
@@ -42,6 +42,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DropTargetBar;
import com.android.launcher3.Launcher;
@@ -49,7 +50,6 @@
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.SpringProperty;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
@@ -340,14 +340,14 @@
if (duration < 0) {
duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
if (dist < maxDist) {
- duration *= DEACCEL_1_5.getInterpolation(dist / maxDist);
+ duration *= DECELERATE_1_5.getInterpolation(dist / maxDist);
}
duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
}
// Fall back to cubic ease out interpolator for the animation if none is specified
TimeInterpolator interpolator =
- motionInterpolator == null ? DEACCEL_1_5 : motionInterpolator;
+ motionInterpolator == null ? DECELERATE_1_5 : motionInterpolator;
// Animate the view
PendingAnimation anim = new PendingAnimation(duration);
@@ -475,7 +475,7 @@
@Override
public void onOverlayScrollChanged(float progress) {
- float alpha = 1 - Interpolators.DEACCEL_3.getInterpolation(progress);
+ float alpha = 1 - Interpolators.DECELERATE_3.getInterpolation(progress);
float transX = getMeasuredWidth() * progress;
if (mIsRtl) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 0d0717e..adfdc89 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -55,9 +55,9 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
@@ -99,7 +99,9 @@
// Whether mAnim has started. Unlike mAnim.isStarted(), this is true even after mAnim ends.
private boolean mScaleAnimStarted;
- private Runnable mOnAnimEndCallback = null;
+ private boolean mShiftAnimStarted;
+ private Runnable mOnScaleAnimEndCallback;
+ private Runnable mOnShiftAnimEndCallback;
private int mLastTouchX;
private int mLastTouchY;
@@ -186,13 +188,26 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- if (mOnAnimEndCallback != null) {
- mOnAnimEndCallback.run();
+ if (mOnScaleAnimEndCallback != null) {
+ mOnScaleAnimEndCallback.run();
}
}
});
// Set up the shift animator.
mShiftAnim = ValueAnimator.ofFloat(0f, 1f);
+ mShiftAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mShiftAnimStarted = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mOnShiftAnimEndCallback != null) {
+ mOnShiftAnimEndCallback.run();
+ }
+ }
+ });
setDragRegion(new Rect(0, 0, width, height));
@@ -211,8 +226,14 @@
setWillNotDraw(false);
}
- public void setOnAnimationEndCallback(Runnable callback) {
- mOnAnimEndCallback = callback;
+ /** Callback invoked when the scale animation ends. */
+ public void setOnScaleAnimEndCallback(Runnable callback) {
+ mOnScaleAnimEndCallback = callback;
+ }
+
+ /** Callback invoked when the shift animation ends. */
+ public void setOnShiftAnimEndCallback(Runnable callback) {
+ mOnShiftAnimEndCallback = callback;
}
/**
@@ -224,11 +245,11 @@
// Load the adaptive icon on a background thread and add the view in ui thread.
MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
Object[] outObj = new Object[1];
+ boolean[] outIsIconThemed = new boolean[1];
int w = mWidth;
int h = mHeight;
Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h,
- true /* shouldThemeIcon */, outObj);
-
+ true /* shouldThemeIcon */, outObj, outIsIconThemed);
if (dr instanceof AdaptiveIconDrawable) {
int blurMargin = (int) mActivity.getResources()
.getDimension(R.dimen.blur_size_medium_outline) / 2;
@@ -237,7 +258,7 @@
bounds.inset(blurMargin, blurMargin);
// Badge is applied after icon normalization so the bounds for badge should not
// be scaled down due to icon normalization.
- mBadge = getBadge(mActivity, info, outObj[0]);
+ mBadge = getBadge(mActivity, info, outObj[0], outIsIconThemed[0]);
FastBitmapDrawable.setBadgeBounds(mBadge, bounds);
// Do not draw the background in case of folder as its translucent
@@ -371,7 +392,7 @@
AnimatorSet anim = new AnimatorSet();
anim.play(ObjectAnimator.ofFloat(newContent, VIEW_ALPHA, 0, 1));
anim.play(ObjectAnimator.ofFloat(mContent, VIEW_ALPHA, 0));
- anim.setDuration(duration).setInterpolator(Interpolators.DEACCEL_1_5);
+ anim.setDuration(duration).setInterpolator(Interpolators.DECELERATE_1_5);
anim.start();
}
@@ -416,10 +437,16 @@
}
}
+ /** {@code true} if the scale animation has finished. */
public boolean isScaleAnimationFinished() {
return mScaleAnimStarted && !mScaleAnim.isRunning();
}
+ /** {@code true} if the shift animation has finished. */
+ public boolean isShiftAnimationFinished() {
+ return mShiftAnimStarted && !mShiftAnim.isRunning();
+ }
+
/**
* Move the window containing this view.
*
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index af43ae8..48b5646 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -31,7 +31,6 @@
import android.widget.RemoteViews;
import com.android.launcher3.DragSource;
-import com.android.launcher3.Launcher;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -72,15 +71,6 @@
}
@Override
- public boolean init(Launcher launcher, boolean alreadyOnHome) {
- super.init(launcher, alreadyOnHome);
- if (!alreadyOnHome) {
- launcher.useFadeOutAnimationForLauncherStart(mCancelSignal);
- }
- return false;
- }
-
- @Override
protected PendingItemDragHelper createDragHelper() {
final PendingAddItemInfo item;
if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 4ae54e6..55a539a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -535,7 +535,7 @@
mFolderName.selectAll();
}
}
- mFolderName.showKeyboard();
+ mFolderName.showKeyboard(true /* shouldFocus */);
mFolderName.displayCompletions(
Stream.of(mInfo.suggestedFolderNames.getLabels())
.filter(Objects::nonNull)
@@ -881,7 +881,6 @@
final ItemInfo item = d.dragInfo;
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT));
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index dd82ecf..9e2e2bf 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -105,11 +105,11 @@
mDelay = res.getInteger(R.integer.config_folderDelay);
mFolderInterpolator = AnimationUtils.loadInterpolator(mContext,
- R.interpolator.folder_interpolator);
+ R.interpolator.standard_interpolator);
mLargeFolderPreviewItemOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
R.interpolator.large_folder_preview_item_open_interpolator);
mLargeFolderPreviewItemCloseInterpolator = AnimationUtils.loadInterpolator(mContext,
- R.interpolator.large_folder_preview_item_close_interpolator);
+ R.interpolator.standard_accelerate_interpolator);
}
/**
@@ -236,9 +236,9 @@
mFolder, startRect, endRect, finalRadius, !mIsOpening));
// Create reveal animator for the folder content (capture the top 4 icons 2x2)
- int width = mDeviceProfile.folderCellLayoutBorderSpacePx
+ int width = mDeviceProfile.folderCellLayoutBorderSpacePx.x
+ mDeviceProfile.folderCellWidthPx * 2;
- int height = mDeviceProfile.folderCellLayoutBorderSpacePx
+ int height = mDeviceProfile.folderCellLayoutBorderSpacePx.y
+ mDeviceProfile.folderCellHeightPx * 2;
int page = mIsOpening ? mContent.getCurrentPage() : mContent.getDestinationPage();
int left = mContent.getPaddingLeft() + page * lp.width;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 2c1100f..53d0efb 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -16,6 +16,7 @@
package com.android.launcher3.folder;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
@@ -42,6 +43,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.Alarm;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
@@ -56,7 +58,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.BaseItemDragListener;
@@ -260,7 +261,6 @@
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
item != mInfo && !mFolder.isOpen());
}
@@ -407,7 +407,7 @@
final int finalIndex = index;
dragLayer.animateView(animateView, to, finalAlpha,
finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
- Interpolators.DEACCEL_2,
+ Interpolators.DECELERATE_2,
() -> {
mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
@@ -628,7 +628,7 @@
Utilities.scaleRectAboutCenter(iconBounds, iconScale);
// If we are animating to the accepting state, animate the dot out.
- mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
+ mDotParams.scale = Math.max(0, mDotScale - mBackground.getAcceptScaleProgress());
mDotParams.dotColor = mBackground.getDotColor();
mDotRenderer.draw(canvas, mDotParams);
}
@@ -802,6 +802,14 @@
}
}
+ @Override
+ public void onHoverChanged(boolean hovered) {
+ super.onHoverChanged(hovered);
+ if (ENABLE_CURSOR_HOVER_STATES.get()) {
+ mBackground.setHovered(hovered);
+ }
+ }
+
/**
* Interface that provides callbacks to a parent ViewGroup that hosts this FolderIcon.
*/
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index 7ac2472..c06a0f3 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -94,9 +94,6 @@
// folder
CellLayout cellLayout = mLauncher.getCellLayout(info.container,
mLauncher.getCellPosMapper().mapModelToPresenter(info).screenId);
- if (cellLayout == null) {
- return;
- }
finalItem = info.contents.remove(0);
newIcon = mLauncher.createShortcut(cellLayout, finalItem);
mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 406955c..b320ceb 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -16,6 +16,8 @@
package com.android.launcher3.folder;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@@ -39,6 +41,9 @@
import android.graphics.Shader;
import android.util.Property;
import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
@@ -55,7 +60,10 @@
private static final boolean DRAW_SHADOW = false;
private static final boolean DRAW_STROKE = false;
- private static final int CONSUMPTION_ANIMATION_DURATION = 100;
+ @VisibleForTesting protected static final int CONSUMPTION_ANIMATION_DURATION = 100;
+
+ @VisibleForTesting protected static final float HOVER_SCALE = 1.1f;
+ @VisibleForTesting protected static final int HOVER_ANIMATION_DURATION = 300;
private final PorterDuffXfermode mShadowPorterDuffXfermode
= new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
@@ -86,17 +94,21 @@
public boolean isClipping = true;
// Drawing / animation configurations
- private static final float ACCEPT_SCALE_FACTOR = 1.20f;
+ @VisibleForTesting protected static final float ACCEPT_SCALE_FACTOR = 1.20f;
// Expressed on a scale from 0 to 255.
private static final int BG_OPACITY = 255;
private static final int MAX_BG_OPACITY = 255;
private static final int SHADOW_OPACITY = 40;
- private ValueAnimator mScaleAnimator;
+ @VisibleForTesting protected ValueAnimator mScaleAnimator;
private ObjectAnimator mStrokeAlphaAnimator;
private ObjectAnimator mShadowAnimator;
+ @VisibleForTesting protected boolean mIsAccepting;
+ @VisibleForTesting protected boolean mIsHovered;
+ @VisibleForTesting protected boolean mIsHoveredOrAnimating;
+
private static final Property<PreviewBackground, Integer> STROKE_ALPHA =
new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") {
@Override
@@ -203,11 +215,11 @@
}
/**
- * Returns the progress of the scale animation, where 0 means the scale is at 1f
- * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
+ * Returns the progress of the scale animation to accept state, where 0 means the scale is at
+ * 1f and 1 means the scale is at ACCEPT_SCALE_FACTOR. Returns 0 when scaled due to hover.
*/
- float getScaleProgress() {
- return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
+ float getAcceptScaleProgress() {
+ return mIsHoveredOrAnimating ? 0 : (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
}
void invalidate() {
@@ -385,60 +397,70 @@
return mDrawingDelegate != null;
}
- private void animateScale(float finalScale, final Runnable onStart, final Runnable onEnd) {
- final float scale0 = mScale;
- final float scale1 = finalScale;
-
+ protected void animateScale(boolean isAccepting, boolean isHovered) {
if (mScaleAnimator != null) {
mScaleAnimator.cancel();
}
- mScaleAnimator = ValueAnimator.ofFloat(0f, 1.0f);
-
- mScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float prog = animation.getAnimatedFraction();
- mScale = prog * scale1 + (1 - prog) * scale0;
- invalidate();
+ final float startScale = mScale;
+ final float endScale = isAccepting ? ACCEPT_SCALE_FACTOR : (isHovered ? HOVER_SCALE : 1f);
+ Interpolator interpolator =
+ isAccepting != mIsAccepting ? ACCELERATE_DECELERATE : EMPHASIZED_DECELERATE;
+ int duration = isAccepting != mIsAccepting ? CONSUMPTION_ANIMATION_DURATION
+ : HOVER_ANIMATION_DURATION;
+ mIsAccepting = isAccepting;
+ mIsHovered = isHovered;
+ if (startScale == endScale) {
+ if (!mIsAccepting) {
+ clearDrawingDelegate();
}
+ mIsHoveredOrAnimating = mIsHovered;
+ return;
+ }
+
+
+ mScaleAnimator = ValueAnimator.ofFloat(0f, 1.0f);
+ mScaleAnimator.addUpdateListener(animation -> {
+ float prog = animation.getAnimatedFraction();
+ mScale = prog * endScale + (1 - prog) * startScale;
+ invalidate();
});
mScaleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- if (onStart != null) {
- onStart.run();
+ if (mIsHovered) {
+ mIsHoveredOrAnimating = true;
}
}
@Override
public void onAnimationEnd(Animator animation) {
- if (onEnd != null) {
- onEnd.run();
+ if (!mIsAccepting) {
+ clearDrawingDelegate();
}
+ mIsHoveredOrAnimating = mIsHovered;
mScaleAnimator = null;
}
});
-
- mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
+ mScaleAnimator.setInterpolator(interpolator);
+ mScaleAnimator.setDuration(duration);
mScaleAnimator.start();
}
public void animateToAccept(CellLayout cl, int cellX, int cellY) {
- animateScale(ACCEPT_SCALE_FACTOR, () -> delegateDrawing(cl, cellX, cellY), null);
+ delegateDrawing(cl, cellX, cellY);
+ animateScale(/* isAccepting= */ true, mIsHovered);
}
public void animateToRest() {
- // This can be called multiple times -- we need to make sure the drawing delegate
- // is saved and restored at the beginning of the animation, since cancelling the
- // existing animation can clear the delgate.
- CellLayout cl = mDrawingDelegate;
- int cellX = mDelegateCellX;
- int cellY = mDelegateCellY;
- animateScale(1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate);
+ animateScale(/* isAccepting= */ false, mIsHovered);
}
public float getStrokeWidth() {
return mStrokeWidth;
}
+
+ protected void setHovered(boolean hovered) {
+ animateScale(mIsAccepting, /* isHovered= */ hovered);
+ }
}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 7457f30..4208343 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,15 +16,19 @@
package com.android.launcher3.graphics;
+import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_APP_ROW;
+
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
import android.view.View;
import androidx.annotation.Nullable;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.BitmapRenderer;
@@ -37,7 +41,6 @@
* A utility class to generate preview bitmap for dragging.
*/
public class DragPreviewProvider {
-
private final Rect mTempRect = new Rect();
protected final View mView;
@@ -99,6 +102,15 @@
height = mView.getHeight();
}
+ if (mView instanceof BubbleTextView btv
+ && btv.getIconDisplay() == DISPLAY_SEARCH_RESULT_APP_ROW) {
+ FastBitmapDrawable icon = ((BubbleTextView) mView).getIcon();
+ Drawable drawable = icon.getConstantState().newDrawable();
+ float xInset = (float) blurSizeOutline / (float) (width + blurSizeOutline);
+ float yInset = (float) blurSizeOutline / (float) (height + blurSizeOutline);
+ return new InsetDrawable(drawable, xInset / 2, yInset / 2, xInset / 2, yInset / 2);
+ }
+
return new FastBitmapDrawable(
BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
height + blurSizeOutline, (c) -> drawDragView(c, scale)));
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 47677ea..ae44f0a 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -34,13 +34,9 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.ContextWrapper;
-import android.content.Intent;
import android.content.res.TypedArray;
-import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -49,12 +45,12 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
+import android.widget.FrameLayout;
import android.widget.TextClock;
import androidx.annotation.NonNull;
@@ -74,12 +70,11 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceLayoutManager;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -98,6 +93,7 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -183,7 +179,6 @@
private final DeviceProfile mDp;
private final DeviceProfile mDpOrig;
private final Rect mInsets;
- private final WorkspaceItemInfo mWorkspaceItemInfo;
private final LayoutInflater mHomeElementInflater;
private final InsettableFrameLayout mRootView;
private final Hotseat mHotseat;
@@ -211,29 +206,9 @@
} else {
mDpOrig = mDp;
}
-
- WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
- .getCurrentWindowMetrics().getWindowInsets();
- mInsets = new Rect(
- currentWindowInsets.getSystemWindowInsetLeft(),
- currentWindowInsets.getSystemWindowInsetTop(),
- currentWindowInsets.getSystemWindowInsetRight(),
- mDp.isTaskbarPresent ? 0 : currentWindowInsets.getSystemWindowInsetBottom());
+ mInsets = getInsets(context);
mDp.updateInsets(mInsets);
- BaseIconFactory iconFactory =
- new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { };
- BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap(
- new AdaptiveIconDrawable(
- new ColorDrawable(Color.WHITE),
- new ColorDrawable(Color.WHITE)));
-
- mWorkspaceItemInfo = new WorkspaceItemInfo();
- mWorkspaceItemInfo.bitmap = iconInfo;
- mWorkspaceItemInfo.intent = new Intent();
- mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
- context.getString(R.string.label_application);
-
mHomeElementInflater = LayoutInflater.from(
new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
mHomeElementInflater.setFactory2(this);
@@ -283,6 +258,26 @@
mAppWidgetHost = new LauncherPreviewAppWidgetHost(context);
}
+ /**
+ * Returns the insets of the screen closest to the display given by the context
+ */
+ private Rect getInsets(Context context) {
+ DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
+ float maxDiff = Float.MAX_VALUE;
+ Display display = context.getDisplay();
+ Rect insets = new Rect();
+ for (WindowBounds supportedBound : info.supportedBounds) {
+ double diff = Math.pow(display.getWidth() - supportedBound.availableSize.x, 2)
+ + Math.pow(display.getHeight() - supportedBound.availableSize.y, 2);
+ if (supportedBound.rotationHint == context.getDisplay().getRotation()
+ && diff < maxDiff) {
+ maxDiff = (float) diff;
+ insets = supportedBound.insets;
+ }
+ }
+ return new Rect(insets);
+ }
+
/** Populate preview and render it. */
public View getRenderedView(BgDataModel dataModel,
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
@@ -378,12 +373,13 @@
addInScreenFromBind(icon, info);
}
- private void inflateAndAddFolder(FolderInfo info) {
+ private void inflateAndAddCollectionIcon(FolderInfo info) {
CellLayout screen = info.container == Favorites.CONTAINER_DESKTOP
? mWorkspaceScreens.get(info.screenId)
: mHotseat;
- FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, screen,
- info);
+ FrameLayout folderIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER
+ ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, info)
+ : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, info);
addInScreenFromBind(folderIcon, info);
}
@@ -483,12 +479,12 @@
for (ItemInfo itemInfo : currentWorkspaceItems) {
switch (itemInfo.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
break;
case Favorites.ITEM_TYPE_FOLDER:
- inflateAndAddFolder((FolderInfo) itemInfo);
+ case Favorites.ITEM_TYPE_APP_PAIR:
+ inflateAndAddCollectionIcon((FolderInfo) itemInfo);
break;
default:
break;
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index d366c4a..307052a 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -17,8 +17,8 @@
package com.android.launcher3.graphics;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V3;
@@ -155,19 +155,19 @@
// Progress color
float[] m3HCT = new float[3];
ColorUtils.colorToM3HCT(primaryIconColor, m3HCT);
- mProgressColor = ColorUtils.M3HCTtoColor(
+ mProgressColor = ColorUtils.M3HCTToColor(
m3HCT[0],
m3HCT[1],
isDarkMode ? Math.max(m3HCT[2], 55) : Math.min(m3HCT[2], 40));
// Track color
- mTrackColor = ColorUtils.M3HCTtoColor(
+ mTrackColor = ColorUtils.M3HCTToColor(
m3HCT[0],
16,
isDarkMode ? 30 : 90
);
// Plate color
- mPlateColor = ColorUtils.M3HCTtoColor(
+ mPlateColor = ColorUtils.M3HCTToColor(
m3HCT[0],
isDarkMode ? 36 : 24,
isDarkMode ? (isThemed() ? 10 : 20) : 80
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index e89c0c5..aebcdd4 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,6 +16,8 @@
package com.android.launcher3.graphics;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -80,11 +82,12 @@
private static final String KEY_DISPLAY_ID = "display_id";
private static final String KEY_COLORS = "wallpaper_colors";
- private final Context mContext;
- private final InvariantDeviceProfile mIdp;
+ private Context mContext;
private final IBinder mHostToken;
private final int mWidth;
private final int mHeight;
+ private String mGridName;
+
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
private final RunnableList mOnDestroyCallbacks = new RunnableList();
@@ -97,15 +100,13 @@
public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
mContext = context;
-
- String gridName = bundle.getString("name");
+ mGridName = bundle.getString("name");
bundle.remove("name");
- if (gridName == null) {
- gridName = InvariantDeviceProfile.getCurrentGridName(context);
+ if (mGridName == null) {
+ mGridName = InvariantDeviceProfile.getCurrentGridName(context);
}
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
- mIdp = new InvariantDeviceProfile(context, gridName);
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
mWidth = bundle.getInt(KEY_VIEW_WIDTH);
@@ -113,9 +114,9 @@
mDisplay = context.getSystemService(DisplayManager.class)
.getDisplay(bundle.getInt(KEY_DISPLAY_ID));
- mSurfaceControlViewHost = MAIN_EXECUTOR
- .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
- .get(5, TimeUnit.SECONDS);
+ mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() -> new SurfaceControlViewHost(mContext,
+ context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY),
+ mHostToken)).get(5, TimeUnit.SECONDS);
mOnDestroyCallbacks.add(mSurfaceControlViewHost::release);
}
@@ -195,28 +196,33 @@
}
}
+ /***
+ * Generates a new context overriding the theme color and the display size without affecting the
+ * main application context
+ */
+ private Context getPreviewContext() {
+ Context context = mContext.createDisplayContext(mDisplay);
+ if (mWallpaperColors == null) {
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context));
+ }
+ if (Utilities.ATLEAST_R) {
+ context = context.createWindowContext(
+ LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+ }
+ LocalColorExtractor.newInstance(context)
+ .applyColorsOverride(context, mWallpaperColors);
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
+ }
+
@WorkerThread
private void loadModelData() {
- final Context inflationContext;
- if (mWallpaperColors != null) {
- // Create a themed context, without affecting the main application context
- Context context = mContext.createDisplayContext(mDisplay);
- if (Utilities.ATLEAST_R) {
- context = context.createWindowContext(
- LayoutParams.TYPE_APPLICATION_OVERLAY, null);
- }
- LocalColorExtractor.newInstance(mContext)
- .applyColorsOverride(context, mWallpaperColors);
- inflationContext = new ContextThemeWrapper(context,
- Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
- } else {
- inflationContext = new ContextThemeWrapper(mContext,
- Themes.getActivityThemeRes(mContext));
- }
-
- if (GridSizeMigrationUtil.needsToMigrate(inflationContext, mIdp)) {
+ final Context inflationContext = getPreviewContext();
+ final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName);
+ if (GridSizeMigrationUtil.needsToMigrate(inflationContext, idp)) {
// Start the migration
- PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
+ PreviewContext previewContext = new PreviewContext(inflationContext, idp);
// Copy existing data to preview DB
LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
.getModel().getModelDbController().getDb(),
@@ -239,7 +245,7 @@
@Override
public void run() {
- DeviceProfile deviceProfile = mIdp.getDeviceProfile(previewContext);
+ DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext);
String query =
LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
+ " or " + LauncherSettings.Favorites.CONTAINER + " = "
@@ -254,7 +260,8 @@
getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
MAIN_EXECUTOR.execute(() -> {
- renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo);
+ renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo,
+ idp);
mOnDestroyCallbacks.add(previewContext::onDestroy);
});
}
@@ -263,7 +270,7 @@
LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
if (dataModel != null) {
MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
- null));
+ null, idp));
} else {
Log.e(TAG, "Model loading failed");
}
@@ -274,11 +281,11 @@
@UiThread
private void renderView(Context inflationContext, BgDataModel dataModel,
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
- @Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
+ @Nullable final SparseArray<Size> launcherWidgetSpanInfo, InvariantDeviceProfile idp) {
if (mDestroyed) {
return;
}
- mRenderer = new LauncherPreviewRenderer(inflationContext, mIdp,
+ mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
mWallpaperColors, launcherWidgetSpanInfo);
mRenderer.hideBottomRow(mHideQsb);
View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index 21ebc98..66001d8 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -13,29 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3.graphics;
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.ObjectAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
-import android.util.FloatProperty;
import android.view.View;
+import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
@@ -46,33 +48,6 @@
*/
public class SysUiScrim implements View.OnAttachStateChangeListener {
- public static final FloatProperty<SysUiScrim> SYSUI_PROGRESS =
- new FloatProperty<SysUiScrim>("sysUiProgress") {
- @Override
- public Float get(SysUiScrim scrim) {
- return scrim.mSysUiProgress;
- }
-
- @Override
- public void setValue(SysUiScrim scrim, float value) {
- scrim.setSysUiProgress(value);
- }
- };
-
- private static final FloatProperty<SysUiScrim> SYSUI_ANIM_MULTIPLIER =
- new FloatProperty<SysUiScrim>("sysUiAnimMultiplier") {
- @Override
- public Float get(SysUiScrim scrim) {
- return scrim.mSysUiAnimMultiplier;
- }
-
- @Override
- public void setValue(SysUiScrim scrim, float value) {
- scrim.mSysUiAnimMultiplier = value;
- scrim.reapplySysUiAlpha();
- }
- };
-
/**
* Receiver used to get a signal that the user unlocked their device.
*/
@@ -92,44 +67,53 @@
}
};
- private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
- private static final int ALPHA_MASK_HEIGHT_DP = 500;
- private static final int ALPHA_MASK_BITMAP_DP = 200;
- private static final int ALPHA_MASK_WIDTH_DP = 2;
+ private static final int MAX_SYSUI_SCRIM_ALPHA = 255;
+ private static final int ALPHA_MASK_BITMAP_WIDTH_DP = 2;
+
+ private static final int BOTTOM_MASK_HEIGHT_DP = 200;
+ private static final int TOP_MASK_HEIGHT_DP = 100;
private boolean mDrawTopScrim, mDrawBottomScrim;
- private final RectF mFinalMaskRect = new RectF();
- private final Paint mBottomMaskPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- private final Bitmap mBottomMask;
- private final int mMaskHeight;
+ private final RectF mTopMaskRect = new RectF();
+ private final Paint mTopMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG);
+ private final Bitmap mTopMaskBitmap;
+ private final int mTopMaskHeight;
+
+ private final RectF mBottomMaskRect = new RectF();
+ private final Paint mBottomMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG);
+ private final Bitmap mBottomMaskBitmap;
+ private final int mBottomMaskHeight;
private final View mRoot;
private final BaseDraggingActivity mActivity;
- private final Drawable mTopScrim;
-
- private float mSysUiProgress = 1;
- private boolean mHideSysUiScrim;
+ private final boolean mHideSysUiScrim;
+ private boolean mSkipScrimAnimationForTest = false;
private boolean mAnimateScrimOnNextDraw = false;
- private float mSysUiAnimMultiplier = 1;
+ private final AnimatedFloat mSysUiAnimMultiplier = new AnimatedFloat(this::reapplySysUiAlpha);
+ private final AnimatedFloat mSysUiProgress = new AnimatedFloat(this::reapplySysUiAlpha);
public SysUiScrim(View view) {
mRoot = view;
mActivity = BaseDraggingActivity.fromContext(view.getContext());
- mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
- view.getResources().getDisplayMetrics());
- mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
- if (mTopScrim != null) {
- mTopScrim.setDither(true);
- mBottomMask = createDitheredAlphaMask();
- mHideSysUiScrim = false;
- } else {
- mBottomMask = null;
- mHideSysUiScrim = true;
- }
+ DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
- view.addOnAttachStateChangeListener(this);
+ mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
+ mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
+ mHideSysUiScrim = Themes.getAttrBoolean(view.getContext(), R.attr.isWorkspaceDarkText);
+
+ mTopMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mTopMaskHeight,
+ new int[]{0x50FFFFFF, 0x0AFFFFFF, 0x00FFFFFF},
+ new float[]{0f, 0.7f, 1f});
+ mTopMaskPaint.setColor(0xFF222222);
+ mBottomMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mBottomMaskHeight,
+ new int[]{0x00FFFFFF, 0x2FFFFFFF},
+ new float[]{0f, 1f});
+
+ if (!KEYGUARD_ANIMATION.get() && !mHideSysUiScrim) {
+ view.addOnAttachStateChangeListener(this);
+ }
}
/**
@@ -137,16 +121,16 @@
*/
public void draw(Canvas canvas) {
if (!mHideSysUiScrim) {
- if (mSysUiProgress <= 0) {
+ if (mSysUiProgress.value <= 0) {
mAnimateScrimOnNextDraw = false;
return;
}
if (mAnimateScrimOnNextDraw) {
- mSysUiAnimMultiplier = 0;
+ mSysUiAnimMultiplier.value = 0;
reapplySysUiAlphaNoInvalidate();
- ObjectAnimator oa = createSysuiMultiplierAnim(1);
+ ObjectAnimator oa = mSysUiAnimMultiplier.animateToValue(1);
oa.setDuration(600);
oa.setStartDelay(mActivity.getWindow().getTransitionBackgroundFadeDuration());
oa.start();
@@ -154,21 +138,26 @@
}
if (mDrawTopScrim) {
- mTopScrim.draw(canvas);
+ canvas.drawBitmap(mTopMaskBitmap, null, mTopMaskRect, mTopMaskPaint);
}
if (mDrawBottomScrim) {
- canvas.drawBitmap(mBottomMask, null, mFinalMaskRect, mBottomMaskPaint);
+ canvas.drawBitmap(mBottomMaskBitmap, null, mBottomMaskRect, mBottomMaskPaint);
}
}
}
/**
- * @return an ObjectAnimator that controls the fade in/out of the sys ui scrim.
+ * Returns the sysui multiplier property for controlling fade in/out of the scrim
*/
- public ObjectAnimator createSysuiMultiplierAnim(float... values) {
- ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, values);
- anim.setAutoCancel(true);
- return anim;
+ public AnimatedFloat getSysUIMultiplier() {
+ return mSysUiAnimMultiplier;
+ }
+
+ /**
+ * Returns the sysui progress property for controlling fade in/out of the scrim
+ */
+ public AnimatedFloat getSysUIProgress() {
+ return mSysUiProgress;
}
/**
@@ -180,44 +169,35 @@
*/
public void onInsetsChanged(Rect insets) {
DeviceProfile dp = mActivity.getDeviceProfile();
- mDrawTopScrim = mTopScrim != null && insets.top > 0;
- mDrawBottomScrim = mBottomMask != null
- && !dp.isVerticalBarLayout()
- && !dp.isGestureMode
- && !dp.isTaskbarPresent;
+ mDrawTopScrim = insets.top > 0;
+ mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.isGestureMode && !dp.isTaskbarPresent;
}
@Override
public void onViewAttachedToWindow(View view) {
- if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
- ScreenOnTracker.INSTANCE.get(mActivity).addListener(mScreenOnListener);
- }
+ ScreenOnTracker.INSTANCE.get(mActivity).addListener(mScreenOnListener);
}
@Override
public void onViewDetachedFromWindow(View view) {
- if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
- ScreenOnTracker.INSTANCE.get(mActivity).removeListener(mScreenOnListener);
- }
+ ScreenOnTracker.INSTANCE.get(mActivity).removeListener(mScreenOnListener);
}
/**
* Set the width and height of the view being scrimmed
- * @param w
- * @param h
*/
public void setSize(int w, int h) {
- if (mTopScrim != null) {
- mTopScrim.setBounds(0, 0, w, h);
- mFinalMaskRect.set(0, h - mMaskHeight, w, h);
- }
+ mTopMaskRect.set(0, 0, w, mTopMaskHeight);
+ mBottomMaskRect.set(0, h - mBottomMaskHeight, w, h);
}
- private void setSysUiProgress(float progress) {
- if (progress != mSysUiProgress) {
- mSysUiProgress = progress;
- reapplySysUiAlpha();
- }
+ /**
+ * Sets whether the SysUiScrim should hide for testing.
+ */
+ @VisibleForTesting
+ public void skipScrimAnimation() {
+ mSkipScrimAnimationForTest = true;
+ reapplySysUiAlpha();
}
private void reapplySysUiAlpha() {
@@ -228,29 +208,22 @@
}
private void reapplySysUiAlphaNoInvalidate() {
- float factor = mSysUiProgress * mSysUiAnimMultiplier;
- mBottomMaskPaint.setAlpha(Math.round(MAX_HOTSEAT_SCRIM_ALPHA * factor));
- if (mTopScrim != null) {
- mTopScrim.setAlpha(Math.round(255 * factor));
- }
+ float factor = mSysUiProgress.value * mSysUiAnimMultiplier.value;
+ if (mSkipScrimAnimationForTest) factor = 1f;
+ mBottomMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
+ mTopMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
}
- private Bitmap createDitheredAlphaMask() {
+ private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) {
DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
- int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
- int gradientHeight = ResourceUtils.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
- Bitmap dst = Bitmap.createBitmap(width, mMaskHeight, Bitmap.Config.ALPHA_8);
+ int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm);
+ Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
Canvas c = new Canvas(dst);
- Paint paint = new Paint(Paint.DITHER_FLAG);
- LinearGradient lg = new LinearGradient(0, 0, 0, gradientHeight,
- new int[]{
- 0x00FFFFFF,
- setColorAlphaBound(Color.WHITE, (int) (0xFF * 0.95)),
- 0xFFFFFFFF},
- new float[]{0f, 0.8f, 1f},
- Shader.TileMode.CLAMP);
+ Paint paint = new Paint(DITHER_FLAG);
+ LinearGradient lg = new LinearGradient(0, 0, 0, height,
+ colors, positions, Shader.TileMode.CLAMP);
paint.setShader(lg);
- c.drawRect(0, 0, width, gradientHeight, paint);
+ c.drawPaint(paint);
return dst;
}
}
diff --git a/src/com/android/launcher3/logging/KeyboardStateManager.java b/src/com/android/launcher3/logging/KeyboardStateManager.java
index 6dc0a0b..d0f9c74 100644
--- a/src/com/android/launcher3/logging/KeyboardStateManager.java
+++ b/src/com/android/launcher3/logging/KeyboardStateManager.java
@@ -24,7 +24,10 @@
*/
public class KeyboardStateManager {
private long mUpdatedTime;
- private int mImeHeight;
+ private int mImeHeightPx;
+ // Height of the keyboard when it's shown.
+ // mImeShownHeightPx>=mImeHeightPx always.
+ private int mImeShownHeightPx;
public enum KeyboardState {
NO_IME_ACTION,
@@ -34,8 +37,9 @@
private KeyboardState mKeyboardState;
- public KeyboardStateManager() {
+ public KeyboardStateManager(int defaultImeShownHeightPx) {
mKeyboardState = NO_IME_ACTION;
+ mImeShownHeightPx = defaultImeShownHeightPx;
}
/**
@@ -64,13 +68,25 @@
* Returns keyboard's current height.
*/
public int getImeHeight() {
- return mImeHeight;
+ return mImeHeightPx;
}
/**
- * Setter method to set keyboard height.
+ * Returns keyboard's height in pixels when shown.
*/
- public void setImeHeight(int imeHeight) {
- mImeHeight = imeHeight;
+ public int getImeShownHeight() {
+ return mImeShownHeightPx;
+ }
+
+ /**
+ * Setter method to set keyboard height in pixels.
+ */
+ public void setImeHeight(int imeHeightPx) {
+ mImeHeightPx = imeHeightPx;
+ if (mImeHeightPx > 0) {
+ // Update the mImeShownHeightPx with the actual ime height when shown and store it
+ // for future sessions.
+ mImeShownHeightPx = mImeHeightPx;
+ }
}
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 66ed779..265378c 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -32,12 +32,9 @@
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.views.ActivityContext;
-import java.util.List;
-
/**
* Handles the user event logging in R+.
*
@@ -59,6 +56,7 @@
private InstanceId mInstanceId;
protected @Nullable ActivityContext mActivityContext = null;
+ protected @Nullable Context mContext = null;
private KeyboardStateManager mKeyboardStateManager;
/**
@@ -623,6 +621,12 @@
@UiEvent(doc = "User has invoked split to left half with a keyboard shortcut.")
LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP(1233),
+ @UiEvent(doc = "User has invoked split to right half from desktop mode.")
+ LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM(1412),
+
+ @UiEvent(doc = "User has invoked split to left half from desktop mode.")
+ LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP(1464),
+
@UiEvent(doc = "User has collapsed the work FAB button by scrolling down in the all apps"
+ " work A-Z list.")
LAUNCHER_WORK_FAB_BUTTON_COLLAPSE(1276),
@@ -643,11 +647,17 @@
@UiEvent(doc = "User has swiped upwards from the gesture handle to show transient taskbar.")
LAUNCHER_TRANSIENT_TASKBAR_SHOW(1331),
+ @UiEvent(doc = "User has clicked an app pair and launched directly into split screen.")
+ LAUNCHER_APP_PAIR_LAUNCH(1374),
+
+ @UiEvent(doc = "User saved an app pair.")
+ LAUNCHER_APP_PAIR_SAVE(1456),
+
@UiEvent(doc = "App launched through pending intent")
- LAUNCHER_APP_LAUNCH_PENDING_INTENT(1394),
- ;
+ LAUNCHER_APP_LAUNCH_PENDING_INTENT(1394)
// ADD MORE
+ ;
private final int mId;
@@ -831,6 +841,10 @@
*/
public interface StatsLatencyLogger {
+ /**
+ * Should be in sync with:
+ * google3/wireless/android/sysui/aster/asterstats/launcher_event_processed.proto
+ */
enum LatencyType {
UNKNOWN(0),
// example: launcher restart that happens via daily backup and restore
@@ -956,33 +970,32 @@
}
/**
- * Sets list of {@link com.android.app.search.ResultType} for the impression event.
+ * Sets {@link com.android.app.search.ResultType} for the impression event.
*/
- default StatsImpressionLogger withResultType(IntArray resultType) {
+ default StatsImpressionLogger withResultType(int resultType) {
return this;
}
/**
- * Sets list of count for each of {@link com.android.app.search.ResultType} for the
- * impression event.
- */
- default StatsImpressionLogger withResultCount(IntArray resultCount) {
- return this;
- }
-
- /**
- * Sets list of boolean for each of {@link com.android.app.search.ResultType} that indicates
+ * Sets boolean for each of {@link com.android.app.search.ResultType} that indicates
* if this result is above keyboard or not for the impression event.
*/
- default StatsImpressionLogger withAboveKeyboard(List<Boolean> aboveKeyboard) {
+ default StatsImpressionLogger withAboveKeyboard(boolean aboveKeyboard) {
return this;
}
/**
- * Sets list of uid for each of {@link com.android.app.search.ResultType} that indicates
+ * Sets uid for each of {@link com.android.app.search.ResultType} that indicates
* package name for the impression event.
*/
- default StatsImpressionLogger withUids(IntArray uid) {
+ default StatsImpressionLogger withUid(int uid) {
+ return this;
+ }
+
+ /**
+ * Sets result source that indicates the origin of the result for the impression event.
+ */
+ default StatsImpressionLogger withResultSource(int resultSource) {
return this;
}
@@ -1031,7 +1044,9 @@
*/
public KeyboardStateManager keyboardStateManager() {
if (mKeyboardStateManager == null) {
- mKeyboardStateManager = new KeyboardStateManager();
+ mKeyboardStateManager = new KeyboardStateManager(
+ mContext != null ? mContext.getResources().getDimensionPixelSize(
+ R.dimen.default_ime_height) : 0);
}
return mKeyboardStateManager;
}
@@ -1067,6 +1082,7 @@
StatsLogManager manager = Overrides.getObject(StatsLogManager.class,
context.getApplicationContext(), R.string.stats_log_manager_class);
manager.mActivityContext = ActivityContext.lookupContextNoThrow(context);
+ manager.mContext = context;
return manager;
}
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 27d1f78..5e86bd6 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -91,8 +91,7 @@
List<ItemInfo> filteredItems = new ArrayList<>();
for (Pair<ItemInfo, Object> entry : mItemList) {
ItemInfo item = entry.first;
- if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
- item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
// Short-circuit this logic if the icon exists somewhere on the workspace
if (shortcutExists(dataModel, item.getIntent(), item.user)) {
continue;
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 5997637..dbb29b8 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.os.Process;
+import android.os.Trace;
import android.util.Log;
import com.android.launcher3.InvariantDeviceProfile;
@@ -83,13 +84,18 @@
* Binds all loaded data to actual views on the main thread.
*/
public void bindWorkspace(boolean incrementBindId, boolean isBindSync) {
- if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
- DisjointWorkspaceBinder workspaceBinder =
+ Trace.beginSection("BaseLauncherBinder#bindWorkspace");
+ try {
+ if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
+ DisjointWorkspaceBinder workspaceBinder =
initWorkspaceBinder(incrementBindId, mBgDataModel.collectWorkspaceScreens());
- workspaceBinder.bindCurrentWorkspacePages(isBindSync);
- workspaceBinder.bindOtherWorkspacePages();
- } else {
- bindWorkspaceAllAtOnce(incrementBindId, isBindSync);
+ workspaceBinder.bindCurrentWorkspacePages(isBindSync);
+ workspaceBinder.bindOtherWorkspacePages();
+ } else {
+ bindWorkspaceAllAtOnce(incrementBindId, isBindSync);
+ }
+ } finally {
+ Trace.endSection();
}
}
@@ -106,6 +112,7 @@
synchronized (mBgDataModel) {
if (incrementBindId) {
mBgDataModel.lastBindId++;
+ mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
}
mMyBindingId = mBgDataModel.lastBindId;
return new DisjointWorkspaceBinder(workspacePages);
@@ -126,6 +133,7 @@
mBgDataModel.extraItems.forEach(extraItems::add);
if (incrementBindId) {
mBgDataModel.lastBindId++;
+ mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
}
mMyBindingId = mBgDataModel.lastBindId;
workspaceItemCount = mBgDataModel.itemsIdMap.size();
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 866e222..97f540e 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -15,9 +15,6 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
-
import android.util.Log;
import androidx.annotation.NonNull;
@@ -33,7 +30,6 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -77,7 +73,6 @@
@Override
public final void run() {
boolean isModelLoaded = Objects.requireNonNull(mModel).isModelLoaded();
- testLogD(WORK_TAB_MISSING, "modelLoaded: " + isModelLoaded + " forTask: " + this);
if (!isModelLoaded) {
if (DEBUG_TASKS) {
Log.d(TAG, "Ignoring model task since loader is pending=" + this);
@@ -115,10 +110,6 @@
List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
.filter(info -> info.id != ItemInfo.NO_ID)
.collect(Collectors.toList());
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "allUpdates: " + allUpdates.size() + ", workspaceUpdates "
- + workspaceUpdates.size());
- }
if (!workspaceUpdates.isEmpty()) {
scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
}
@@ -157,12 +148,7 @@
}
public void bindApplicationsIfNeeded() {
- boolean changeFlag = mAllAppsList.getAndResetChangeFlag();
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "bindApplicationsIfNeeded changeFlag? " +
- changeFlag);
- }
- if (changeFlag) {
+ if (mAllAppsList.getAndResetChangeFlag()) {
AppInfo[] apps = mAllAppsList.copyData();
int flags = mAllAppsList.getFlags();
Map<PackageUserKey, Integer> packageUserKeytoUidMap = Arrays.stream(apps).collect(
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index d94df51..7bcd038 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -126,6 +126,11 @@
public int lastBindId = 0;
/**
+ * Load id for which the callbacks were successfully bound
+ */
+ public int lastLoadId = -1;
+
+ /**
* Clears all the data
*/
public synchronized void clear() {
@@ -211,7 +216,6 @@
// Fall through.
}
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
workspaceItems.remove(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -246,7 +250,6 @@
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
workspaceItems.add(item);
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index edc8c1b..f24d1d2 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -33,6 +33,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import java.util.Locale;
import java.util.Objects;
@@ -92,6 +93,9 @@
* Stores the device state to shared preferences
*/
public void writeToPrefs(Context context) {
+ if (context instanceof SandboxContext) {
+ return;
+ }
LauncherPrefs.get(context).put(
WORKSPACE_SIZE.to(mGridSizeString),
HOTSEAT_COUNT.to(mNumHotseat),
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index 9a6cde6..c233872 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -45,7 +45,6 @@
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
@@ -133,10 +132,8 @@
Log.v(TAG, "Workspace migration completed in "
+ (System.currentTimeMillis() - migrationStartTime));
- if (!(context instanceof SandboxContext)) {
- // Save current configuration, so that the migration does not run again.
- destDeviceState.writeToPrefs(context);
- }
+ // Save current configuration, so that the migration does not run again.
+ destDeviceState.writeToPrefs(context);
}
}
@@ -455,7 +452,6 @@
try {
// calculate weight
switch (entry.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent);
@@ -531,7 +527,6 @@
try {
// calculate weight
switch (entry.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent);
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index fa0511c..9a3abd4 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -286,7 +286,6 @@
final WorkspaceItemInfo si = new WorkspaceItemInfo();
si.user = user;
- si.itemType = ITEM_TYPE_APPLICATION;
LauncherActivityInfo lai;
boolean usePackageIcon = laiList.isEmpty();
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 2054d93..33332f0 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -193,9 +193,7 @@
public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
WorkspaceItemInfo wai, boolean useLowResIcon) {
- byte[] iconBlob = itemType == Favorites.ITEM_TYPE_SHORTCUT
- || itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
- || restoreFlag != 0
+ byte[] iconBlob = itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0
? getIconBlob() : null;
return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
@@ -347,7 +345,6 @@
}
final WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.itemType = Favorites.ITEM_TYPE_APPLICATION;
info.user = user;
info.intent = newIntent;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index ca356b0..0e68db2 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -24,8 +24,6 @@
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
@@ -33,7 +31,6 @@
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -61,7 +58,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
@@ -203,7 +199,7 @@
}
}
- Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
+ TraceHelper.INSTANCE.beginSection(TAG);
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
@@ -327,7 +323,7 @@
memoryLogger.printLogs();
throw e;
}
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
public synchronized void stopLocked() {
@@ -361,25 +357,15 @@
String selection,
@Nullable LoaderMemoryLogger memoryLogger) {
final Context context = mApp.getContext();
- final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSafeMode = pmHelper.isSafeMode();
final boolean isSdCardReady = Utilities.isBootCompleted();
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
- boolean clearDb = false;
- if (!mApp.getModel().getModelDbController().migrateGridIfNeeded()) {
- // Migration failed. Clear workspace.
- clearDb = true;
- }
-
- if (clearDb) {
- Log.d(TAG, "loadWorkspace: resetting launcher database");
- Settings.call(contentResolver, Settings.METHOD_CREATE_EMPTY_DB);
- }
-
+ ModelDbController dbController = mApp.getModel().getModelDbController();
+ dbController.tryMigrateDB();
Log.d(TAG, "loadWorkspace: loading default favorites");
- Settings.call(contentResolver, Settings.METHOD_LOAD_DEFAULT_FAVORITES);
+ dbController.loadDefaultFavoritesIfNecessary();
synchronized (mBgDataModel) {
mBgDataModel.clear();
@@ -388,17 +374,18 @@
final HashMap<PackageUserKey, SessionInfo> installingPkgs =
mSessionHelper.getActiveSessions();
installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
+ FileLog.d(TAG, "loadWorkspace: Packages with active install sessions: "
+ + installingPkgs.values());
final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
mShortcutKeyToPinnedShortcuts = new HashMap<>();
- ModelDbController dbController = mApp.getModel().getModelDbController();
final LoaderCursor c = new LoaderCursor(
dbController.query(TABLE_NAME, null, selection, null, null),
mApp, mUserManagerState);
final Bundle extras = c.getExtras();
- mDbName = extras == null ? null : extras.getString(Settings.EXTRA_DB_NAME);
+ mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME);
try {
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
@@ -505,7 +492,6 @@
boolean allowMissingTarget = false;
switch (c.itemType) {
- case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_APPLICATION:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
Intent intent = c.parseIntent();
@@ -519,9 +505,8 @@
ComponentName cn = intent.getComponent();
String targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
- if (TextUtils.isEmpty(targetPkg)
- && c.itemType != Favorites.ITEM_TYPE_SHORTCUT) {
- c.markDeleted("Only legacy shortcuts can have null package");
+ if (TextUtils.isEmpty(targetPkg)) {
+ c.markDeleted("Shortcuts can't have null package");
return;
}
@@ -707,9 +692,11 @@
break;
case Favorites.ITEM_TYPE_FOLDER:
+ case Favorites.ITEM_TYPE_APP_PAIR:
FolderInfo folderInfo = mBgDataModel.findOrMakeFolder(c.id);
c.applyCommonProperties(folderInfo);
+ folderInfo.itemType = c.itemType;
// Do not trim the folder label, as is was set by the user.
folderInfo.title = c.getString(c.mTitleIndex);
folderInfo.spanX = 1;
@@ -917,9 +904,7 @@
private void sanitizeFolders(boolean itemsDeleted) {
if (itemsDeleted) {
// Remove any empty folder
- int[] deletedFolderIds = Settings.call(mApp.getContext().getContentResolver(),
- Settings.METHOD_DELETE_EMPTY_FOLDERS)
- .getIntArray(Settings.EXTRA_VALUE);
+ IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
synchronized (mBgDataModel) {
for (int folderId : deletedFolderIds) {
mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
@@ -932,11 +917,9 @@
private void sanitizeWidgetsShortcutsAndPackages() {
Context context = mApp.getContext();
- ContentResolver contentResolver = context.getContentResolver();
// Remove any ghost widgets
- Settings.call(contentResolver,
- Settings.METHOD_REMOVE_GHOST_WIDGETS);
+ mApp.getModel().getModelDbController().removeGhostWidgets();
// Update pinned state of model shortcuts
mBgDataModel.updateShortcutPinnedState(context);
@@ -951,7 +934,6 @@
}
private List<LauncherActivityInfo> loadAllApps() {
- testLogD(WORK_TAB_MISSING, "loadingAllApps");
final List<UserHandle> profiles = mUserCache.getUserProfiles();
List<LauncherActivityInfo> allActivityList = new ArrayList<>();
// Clear the list of apps
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index f0e5ef6..e10e72d 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -37,7 +37,6 @@
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -85,6 +84,7 @@
private static final String TAG = "LauncherProvider";
private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
+ public static final String EXTRA_DB_NAME = "db_name";
protected DatabaseHelper mOpenHelper;
@@ -140,7 +140,7 @@
table, projection, selection, selectionArgs, null, null, sortOrder);
final Bundle extra = new Bundle();
- extra.putString(LauncherSettings.Settings.EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
+ extra.putString(EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
result.setExtras(extra);
return result;
}
@@ -162,28 +162,6 @@
}
/**
- * Similar to insert but for adding multiple values in a transaction.
- */
- @WorkerThread
- public int bulkInsert(String table, ContentValues[] values) {
- createDbIfNotExists();
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- int numValues = values.length;
- for (int i = 0; i < numValues; i++) {
- addModifiedTime(values[i]);
- if (mOpenHelper.dbInsertAndCheck(db, table, values[i]) < 0) {
- return 0;
- }
- }
- onAddOrDeleteOp(db);
- t.commit();
- }
- return values.length;
- }
-
- /**
* Refer {@link SQLiteDatabase#delete(String, String, String[])}
*/
@WorkerThread
@@ -191,10 +169,6 @@
createDbIfNotExists();
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- if (Binder.getCallingPid() != Process.myPid()
- && Favorites.TABLE_NAME.equalsIgnoreCase(table)) {
- mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
- }
int count = db.delete(table, selection, selectionArgs);
if (count > 0) {
onAddOrDeleteOp(db);
@@ -250,6 +224,7 @@
public void createEmptyDB() {
createDbIfNotExists();
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
+ LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey().to(true));
}
/**
@@ -280,14 +255,34 @@
mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
}
+
+ /**
+ * Migrates the DB if needed. If the migration failed, it clears the DB.
+ */
+ public void tryMigrateDB() {
+ if (!migrateGridIfNeeded()) {
+ Log.d(TAG, "Migration failed: resetting launcher database");
+ createEmptyDB();
+ LauncherPrefs.get(mContext).putSync(
+ getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
+
+ // Write the grid state to avoid another migration
+ new DeviceGridState(LauncherAppState.getIDP(mContext)).writeToPrefs(mContext);
+ }
+ }
+
/**
* Migrates the DB if needed, and returns false if the migration failed
* and DB needs to be cleared.
* @return true if migration was success or ignored, false if migration failed
* and the DB should be reset.
*/
- public boolean migrateGridIfNeeded() {
+ private boolean migrateGridIfNeeded() {
createDbIfNotExists();
+ if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
+ // If we have already create a new DB, ignore migration
+ return false;
+ }
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
return true;
@@ -358,7 +353,7 @@
}
private void clearFlagEmptyDbCreated() {
- LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()));
+ LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey());
}
/**
@@ -372,7 +367,7 @@
public synchronized void loadDefaultFavoritesIfNecessary() {
createDbIfNotExists();
- if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()))) {
+ if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
Log.d(TAG, "loading default workspace");
LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
@@ -471,7 +466,7 @@
() -> parser, AutoInstallsLayout.TAG_WORKSPACE);
}
- private static Uri getLayoutUri(String authority, Context ctx) {
+ public static Uri getLayoutUri(String authority, Context ctx) {
InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
.appendQueryParameter("version", "1")
@@ -491,6 +486,10 @@
mOpenHelper, mContext.getResources(), defaultLayout);
}
+ private ConstantItem<Boolean> getEmptyDbCreatedKey() {
+ return getEmptyDbCreatedKey(mOpenHelper.getDatabaseName());
+ }
+
/**
* Re-composite given key in respect to database. If the current db is
* {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index ddb8b05..2358a9f 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,13 +16,12 @@
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
-import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
@@ -32,9 +31,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.Utilities;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -45,6 +42,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -80,7 +78,7 @@
private final boolean mVerifyChanges;
// Keep track of delete operations that occur when an Undo option is present; we may not commit.
- private final List<Runnable> mDeleteRunnables = new ArrayList<>();
+ private final List<ModelTask> mDeleteRunnables = new ArrayList<>();
private boolean mPreparingToUndo;
private final CellPosMapper mCellPosMapper;
@@ -217,8 +215,7 @@
item.spanX = spanX;
item.spanY = spanY;
notifyItemModified(item);
-
- MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () ->
+ new UpdateItemRunnable(item, () ->
new ContentWriter(mContext)
.put(Favorites.CONTAINER, item.container)
.put(Favorites.CELLX, item.cellX)
@@ -226,7 +223,8 @@
.put(Favorites.RANK, item.rank)
.put(Favorites.SPANX, item.spanX)
.put(Favorites.SPANY, item.spanY)
- .put(Favorites.SCREEN, item.screenId)));
+ .put(Favorites.SCREEN, item.screenId))
+ .executeOnModelThread();
}
/**
@@ -234,11 +232,11 @@
*/
public void updateItemInDatabase(ItemInfo item) {
notifyItemModified(item);
- MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () -> {
+ new UpdateItemRunnable(item, () -> {
ContentWriter writer = new ContentWriter(mContext);
item.onAddToDatabase(writer);
return writer;
- }));
+ }).executeOnModelThread();
}
private void notifyItemModified(ItemInfo item) {
@@ -253,13 +251,12 @@
int container, int screenId, int cellX, int cellY) {
updateItemInfoProps(item, container, screenId, cellX, cellY);
- final ContentResolver cr = mContext.getContentResolver();
- item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE);
+ item.id = mModel.getModelDbController().generateNewItemId();
notifyOtherCallbacks(c -> c.bindItems(Collections.singletonList(item), false));
ModelVerifier verifier = new ModelVerifier();
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- MODEL_EXECUTOR.execute(() -> {
+ newModelTask(() -> {
// Write the item on background thread, as some properties might have been updated in
// the background.
final ContentWriter writer = new ContentWriter(mContext);
@@ -272,7 +269,7 @@
mBgDataModel.addItem(mContext, item, true);
verifier.verifyModel();
}
- });
+ }).executeOnModelThread();
}
/**
@@ -303,15 +300,13 @@
Collectors.joining(","))
+ ". Reason: [" + (TextUtils.isEmpty(reason) ? "unknown" : reason) + "]");
notifyDelete(items);
- enqueueDeleteRunnable(() -> {
+ enqueueDeleteRunnable(newModelTask(() -> {
for (ItemInfo item : items) {
- final Uri uri = Favorites.getContentUri(item.id);
- mContext.getContentResolver().delete(uri, null, null);
-
+ mModel.getModelDbController().delete(TABLE_NAME, itemIdMatch(item.id), null);
mBgDataModel.removeItem(mContext, item);
verifier.verifyModel();
}
- });
+ }));
}
/**
@@ -321,7 +316,7 @@
ModelVerifier verifier = new ModelVerifier();
notifyDelete(Collections.singleton(info));
- enqueueDeleteRunnable(() -> {
+ enqueueDeleteRunnable(newModelTask(() -> {
mModel.getModelDbController().delete(Favorites.TABLE_NAME,
Favorites.CONTAINER + "=" + info.id, null);
mBgDataModel.removeItem(mContext, info.contents);
@@ -331,7 +326,7 @@
Favorites._ID + "=" + info.id, null);
mBgDataModel.removeItem(mContext, info);
verifier.verifyModel();
- });
+ }));
}
/**
@@ -343,7 +338,7 @@
if (holder != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) {
// Deleting an app widget ID is a void call but writes to disk before returning
// to the caller...
- enqueueDeleteRunnable(() -> holder.deleteAppWidgetId(info.appWidgetId));
+ enqueueDeleteRunnable(newModelTask(() -> holder.deleteAppWidgetId(info.appWidgetId)));
}
deleteItemFromDatabase(info, reason);
}
@@ -373,19 +368,17 @@
* {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called).
* Otherwise, we run the Runnable immediately.
*/
- private void enqueueDeleteRunnable(Runnable r) {
+ private void enqueueDeleteRunnable(ModelTask r) {
if (mPreparingToUndo) {
mDeleteRunnables.add(r);
} else {
- MODEL_EXECUTOR.execute(r);
+ r.executeOnModelThread();
}
}
public void commitDelete() {
mPreparingToUndo = false;
- for (Runnable runnable : mDeleteRunnables) {
- MODEL_EXECUTOR.execute(runnable);
- }
+ mDeleteRunnables.forEach(ModelTask::executeOnModelThread);
mDeleteRunnables.clear();
}
@@ -426,10 +419,9 @@
}
@Override
- public void run() {
- Uri uri = Favorites.getContentUri(mItemId);
- mContext.getContentResolver().update(uri, mWriter.get().getValues(mContext),
- null, null);
+ public void runImpl() {
+ mModel.getModelDbController().update(
+ TABLE_NAME, mWriter.get().getValues(mContext), itemIdMatch(mItemId), null);
updateItemArrays(mItem, mItemId);
}
}
@@ -444,27 +436,24 @@
}
@Override
- public void run() {
- ArrayList<ContentProviderOperation> ops = new ArrayList<>();
- int count = mItems.size();
- for (int i = 0; i < count; i++) {
- ItemInfo item = mItems.get(i);
- final int itemId = item.id;
- final Uri uri = Favorites.getContentUri(itemId);
- ContentValues values = mValues.get(i);
-
- ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
- updateItemArrays(item, itemId);
- }
- try {
- mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
+ public void runImpl() {
+ try (SQLiteTransaction t = mModel.getModelDbController().newTransaction()) {
+ int count = mItems.size();
+ for (int i = 0; i < count; i++) {
+ ItemInfo item = mItems.get(i);
+ final int itemId = item.id;
+ mModel.getModelDbController().update(
+ TABLE_NAME, mValues.get(i), itemIdMatch(itemId), null);
+ updateItemArrays(item, itemId);
+ }
+ t.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- private abstract class UpdateItemBaseRunnable implements Runnable {
+ private abstract class UpdateItemBaseRunnable extends ModelTask {
private final StackTraceElement[] mStackTrace;
private final ModelVerifier mVerifier = new ModelVerifier();
@@ -498,9 +487,9 @@
modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
switch (modelItem.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_FOLDER:
+ case Favorites.ITEM_TYPE_APP_PAIR:
if (!mBgDataModel.workspaceItems.contains(modelItem)) {
mBgDataModel.workspaceItems.add(modelItem);
}
@@ -516,6 +505,35 @@
}
}
+ private abstract class ModelTask implements Runnable {
+
+ private final int mLoadId = mBgDataModel.lastLoadId;
+
+ @Override
+ public final void run() {
+ if (mLoadId != mModel.getLastLoadId()) {
+ Log.d(TAG, "Model changed before the task could execute");
+ return;
+ }
+ runImpl();
+ }
+
+ public final void executeOnModelThread() {
+ MODEL_EXECUTOR.execute(this);
+ }
+
+ public abstract void runImpl();
+ }
+
+ private ModelTask newModelTask(Runnable r) {
+ return new ModelTask() {
+ @Override
+ public void runImpl() {
+ r.run();
+ }
+ };
+ }
+
/**
* Utility class to verify model updates are propagated properly to the callback.
*/
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 8c938f4..2591550 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -93,11 +93,6 @@
mOp = op;
mUser = user;
mPackages = packages;
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.WORK_TAB_MISSING, "PackageUpdatedTask mOp: " + mOp +
- " packageCount: " + mPackages.length + " user: " + user);
- DEBUG = true;
- }
}
@Override
@@ -142,9 +137,6 @@
// The update may have changed which shortcuts/widgets are available.
// Refresh the widgets for the package if we have an activity running.
Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.WORK_TAB_MISSING, "launcher: " + launcher);
- }
if (launcher != null) {
launcher.refreshAndBindWidgetsForPackageUser(
new PackageUserKey(packages[i], mUser));
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index cb78138..63ca35b 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -16,12 +16,10 @@
package com.android.launcher3.model;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
-import android.util.Log;
import androidx.annotation.NonNull;
@@ -31,7 +29,6 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -63,10 +60,6 @@
if (mIsUserUnlocked) {
QueryResult shortcuts = new ShortcutRequest(context, mUser)
.query(ShortcutRequest.PINNED);
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "shortcutQuery success? "
- + shortcuts.wasSuccess());
- }
if (shortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : shortcuts) {
pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
@@ -89,9 +82,6 @@
if (mIsUserUnlocked) {
ShortcutKey key = ShortcutKey.fromItemInfo(si);
ShortcutInfo shortcut = pinnedShortcuts.get(key);
- if (TestProtocol.sDebugTracing) {
- Log.d(WORK_TAB_MISSING, "shortcutInfo: " + shortcut);
- }
// We couldn't verify the shortcut during loader. If its no longer available
// (probably due to clear data), delete the workspace item as well
if (shortcut == null) {
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
index 93fc6a5..1fc8a03 100644
--- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -82,9 +82,7 @@
if (!found) {
// Still no position found. Add a new screen to the end.
- screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ screenId = app.getModel().getModelDbController().getNewScreenId();
// Save the screen id for binding in the workspace
workspaceScreens.add(screenId);
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index e5a0eb1..9bf6d43 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -119,8 +119,8 @@
public static FolderInfo createAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
FolderInfo newAppPair = new FolderInfo();
newAppPair.itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
- newAppPair.contents.add(app1);
- newAppPair.contents.add(app2);
+ newAppPair.add(app1, /* animate */ false);
+ newAppPair.add(app2, /* animate */ false);
return newAppPair;
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 660929c..9afa459 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -30,7 +30,6 @@
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID;
@@ -50,7 +49,6 @@
import com.android.launcher3.LauncherSettings.Animation;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.AllAppsContainer;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
@@ -88,7 +86,6 @@
/**
* One of {@link Favorites#ITEM_TYPE_APPLICATION},
- * {@link Favorites#ITEM_TYPE_SHORTCUT},
* {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT}
* {@link Favorites#ITEM_TYPE_FOLDER},
* {@link Favorites#ITEM_TYPE_APP_PAIR},
@@ -325,9 +322,7 @@
* Returns whether this item should use the background animation.
*/
public boolean shouldUseBackgroundAnimation() {
- return animationType == LauncherSettings.Animation.VIEW_BACKGROUND
- && FeatureFlags.ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES.get()
- && FeatureFlags.ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION.get();
+ return animationType == LauncherSettings.Animation.VIEW_BACKGROUND;
}
/**
@@ -368,13 +363,6 @@
})
.orElse(LauncherAtom.Shortcut.newBuilder()));
break;
- case ITEM_TYPE_SHORTCUT:
- itemBuilder
- .setShortcut(nullableComponent
- .map(component -> LauncherAtom.Shortcut.newBuilder()
- .setShortcutName(component.flattenToShortString()))
- .orElse(LauncherAtom.Shortcut.newBuilder()));
- break;
case ITEM_TYPE_APPWIDGET:
itemBuilder
.setWidget(nullableComponent
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 01606d4..3ce194d 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -96,7 +96,7 @@
public WorkspaceItemInfo() {
- itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
}
public WorkspaceItemInfo(WorkspaceItemInfo info) {
@@ -205,8 +205,8 @@
@Override
public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent();
- if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT || hasStatusFlag(
- FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON))) {
+ if (cn == null && hasStatusFlag(
+ FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON)) {
// Legacy shortcuts and promise icons with web UI may not have a componentName but just
// a packageName. In that case create a empty componentName instead of adding additional
// check everywhere.
diff --git a/src/com/android/launcher3/notification/NotificationContainer.java b/src/com/android/launcher3/notification/NotificationContainer.java
index 9eb05cd..7cc9ad3 100644
--- a/src/com/android/launcher3/notification/NotificationContainer.java
+++ b/src/com/android/launcher3/notification/NotificationContainer.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.notification;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
import android.animation.Animator;
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index bb2c37f..f4468fd 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ACTION_POPUP;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS;
+import static com.android.launcher3.Utilities.allowBGLaunch;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_LAUNCH_TAP;
import android.app.ActivityOptions;
@@ -26,7 +27,6 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.view.View;
@@ -103,10 +103,10 @@
return;
}
final ActivityContext context = ActivityContext.lookupContext(view.getContext());
- Bundle activityOptions = ActivityOptions.makeClipRevealAnimation(
- view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
+ ActivityOptions options = allowBGLaunch(ActivityOptions.makeClipRevealAnimation(
+ view, 0, 0, view.getWidth(), view.getHeight()));
try {
- intent.send(null, 0, null, null, null, null, activityOptions);
+ intent.send(null, 0, null, null, null, null, options.toBundle());
context.getStatsLogManager().logger().withItemInfo(mItemInfo)
.log(LAUNCHER_NOTIFICATION_LAUNCH_TAP);
} catch (PendingIntent.CanceledException e) {
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 16a4057..ecd018b 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -16,8 +16,8 @@
package com.android.launcher3.notification;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Utilities.mapToRange;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DISMISSED;
import android.animation.AnimatorSet;
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index 570d6ff..4ab2037 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -27,6 +27,14 @@
void setMarkersCount(int numMarkers);
/**
+ * Sets flag to indicate when the screens are in the process of binding so that we don't animate
+ * during that period.
+ */
+ default void setAreScreensBinding(boolean areScreensBinding) {
+ // No-op by default
+ }
+
+ /**
* Sets the flag if the Page Indicator should autohide.
*/
default void setShouldAutoHide(boolean shouldAutoHide) {
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index b2c64b3..ce71275 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -130,6 +130,7 @@
*/
private float mCurrentPosition;
private float mFinalPosition;
+ private boolean mAreScreensBinding;
private ObjectAnimator mAnimator;
private @Nullable ObjectAnimator mAlphaAnimator;
@@ -163,7 +164,7 @@
@Override
public void setScroll(int currentScroll, int totalScroll) {
- if (SHOW_DOT_PAGINATION.get() && mActivePage != 0 && currentScroll == 0) {
+ if (SHOW_DOT_PAGINATION.get() && currentScroll == 0 && totalScroll == 0) {
CURRENT_POSITION.set(this, (float) mActivePage);
return;
}
@@ -172,6 +173,11 @@
return;
}
+ // Skip scroll update during binding. We will update it when binding completes.
+ if (mAreScreensBinding) {
+ return;
+ }
+
if (mShouldAutoHide) {
animatePaginationToAlpha(VISIBLE_ALPHA);
}
@@ -359,6 +365,16 @@
}
@Override
+ public void setAreScreensBinding(boolean areScreensBinding) {
+ // Reapply correct current position which was skipped during setScroll.
+ if (mAreScreensBinding && !areScreensBinding) {
+ CURRENT_POSITION.set(this, (float) mActivePage);
+ }
+
+ mAreScreensBinding = areScreensBinding;
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Add extra spacing of mDotRadius on all sides so than entry animation could be run.
int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
@@ -381,7 +397,9 @@
// Draw all page indicators;
float circleGap = mCircleGap;
- float startX = (getWidth() - (mNumPages * circleGap) + mDotRadius) / 2;
+ float startX = ((float) getWidth() / 2)
+ - (mCircleGap * (((float) mNumPages - 1) / 2))
+ - mDotRadius;
float x = startX + mDotRadius;
float y = getHeight() / 2;
@@ -420,9 +438,9 @@
float startCircle = (int) mCurrentPosition;
float delta = mCurrentPosition - startCircle;
float diameter = 2 * mDotRadius;
- float startX;
-
- startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
+ float startX = ((float) getWidth() / 2)
+ - (mCircleGap * (((float) mNumPages - 1) / 2))
+ - mDotRadius;
sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
sTempRect.left = startX + (startCircle * mCircleGap);
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index b24ee34..351ebce 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -16,8 +16,11 @@
package com.android.launcher3.pm;
+import static com.android.launcher3.Utilities.allowBGLaunch;
+
import android.annotation.TargetApi;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -72,7 +75,7 @@
}
public int getItemType() {
- return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ return LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
}
@Override
@@ -138,8 +141,10 @@
}
IntentSender is = activity.getSystemService(LauncherApps.class)
.getShortcutConfigActivityIntent(mInfo);
+ ActivityOptions options = allowBGLaunch(ActivityOptions.makeBasic());
try {
- activity.startIntentSenderForResult(is, requestCode, null, 0, 0, 0);
+ activity.startIntentSenderForResult(is, requestCode, null, 0, 0, 0,
+ options.toBundle());
return true;
} catch (IntentSender.SendIntentException e) {
Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 24a9609..92822ab 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -16,16 +16,19 @@
package com.android.launcher3.pm;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
+import static com.android.launcher3.Utilities.ATLEAST_U;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import android.content.Intent;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
-import android.util.LongSparseArray;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
@@ -34,136 +37,121 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
/**
* Class which manages a local cache of user handles to avoid system rpc
*/
-public class UserCache {
+public class UserCache implements SafeCloseable {
+
+ public static final String ACTION_PROFILE_ADDED = ATLEAST_U
+ ? Intent.ACTION_PROFILE_ADDED : Intent.ACTION_MANAGED_PROFILE_ADDED;
+ public static final String ACTION_PROFILE_REMOVED = ATLEAST_U
+ ? Intent.ACTION_PROFILE_REMOVED : Intent.ACTION_MANAGED_PROFILE_REMOVED;
+
+ public static final String ACTION_PROFILE_UNLOCKED = ATLEAST_U
+ ? Intent.ACTION_PROFILE_ACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNLOCKED;
+ public static final String ACTION_PROFILE_LOCKED = ATLEAST_U
+ ? Intent.ACTION_PROFILE_INACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
public static final MainThreadInitializedObject<UserCache> INSTANCE =
new MainThreadInitializedObject<>(UserCache::new);
- private final Context mContext;
- private final UserManager mUserManager;
- private final ArrayList<Runnable> mUserChangeListeners = new ArrayList<>();
+ private final List<BiConsumer<UserHandle, String>> mUserEventListeners = new ArrayList<>();
private final SimpleBroadcastReceiver mUserChangeReceiver =
new SimpleBroadcastReceiver(this::onUsersChanged);
- private LongSparseArray<UserHandle> mUsers;
- // Create a separate reverse map as LongSparseArray.indexOfValue checks if objects are same
- // and not {@link Object#equals}
- private ArrayMap<UserHandle, Long> mUserToSerialMap;
+ private final Context mContext;
+
+ @NonNull
+ private Map<UserHandle, Long> mUserToSerialMap;
private UserCache(Context context) {
mContext = context;
- mUserManager = context.getSystemService(UserManager.class);
+ mUserToSerialMap = Collections.emptyMap();
+ MODEL_EXECUTOR.execute(this::initAsync);
}
+ @Override
+ public void close() {
+ MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
+ }
+
+ @WorkerThread
+ private void initAsync() {
+ mUserChangeReceiver.register(mContext,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+ ACTION_PROFILE_ADDED,
+ ACTION_PROFILE_REMOVED,
+ ACTION_PROFILE_UNLOCKED,
+ ACTION_PROFILE_LOCKED);
+ updateCache();
+ }
+
+ @AnyThread
private void onUsersChanged(Intent intent) {
- testLogD(WORK_TAB_MISSING, "onUsersChanged intent: " + intent);
- enableAndResetCache();
- mUserChangeListeners.forEach(Runnable::run);
+ MODEL_EXECUTOR.execute(this::updateCache);
+ UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (user == null) {
+ return;
+ }
+ String action = intent.getAction();
+ mUserEventListeners.forEach(l -> l.accept(user, action));
+ }
+
+ @WorkerThread
+ private void updateCache() {
+ mUserToSerialMap = queryAllUsers(mContext.getSystemService(UserManager.class));
}
/**
* Adds a listener for user additions and removals
*/
- public SafeCloseable addUserChangeListener(Runnable command) {
- synchronized (this) {
- if (mUserChangeListeners.isEmpty()) {
- // Enable caching and start listening for user broadcast
- mUserChangeReceiver.register(mContext,
- Intent.ACTION_MANAGED_PROFILE_ADDED,
- Intent.ACTION_MANAGED_PROFILE_REMOVED);
- enableAndResetCache();
- }
- mUserChangeListeners.add(command);
- return () -> removeUserChangeListener(command);
- }
- }
-
- private void enableAndResetCache() {
- synchronized (this) {
- mUsers = new LongSparseArray<>();
- mUserToSerialMap = new ArrayMap<>();
- List<UserHandle> users = mUserManager.getUserProfiles();
- if (users != null) {
- for (UserHandle user : users) {
- testLogD(WORK_TAB_MISSING, "caching user: " + user);
- long serial = mUserManager.getSerialNumberForUser(user);
- mUsers.put(serial, user);
- mUserToSerialMap.put(user, serial);
- }
- }
- }
- }
-
- private void removeUserChangeListener(Runnable command) {
- synchronized (this) {
- mUserChangeListeners.remove(command);
- if (mUserChangeListeners.isEmpty()) {
- // Disable cache and stop listening
- mContext.unregisterReceiver(mUserChangeReceiver);
-
- mUsers = null;
- mUserToSerialMap = null;
- }
- }
+ public SafeCloseable addUserEventListener(BiConsumer<UserHandle, String> listener) {
+ mUserEventListeners.add(listener);
+ return () -> mUserEventListeners.remove(listener);
}
/**
* @see UserManager#getSerialNumberForUser(UserHandle)
*/
public long getSerialNumberForUser(UserHandle user) {
- synchronized (this) {
- if (mUserToSerialMap != null) {
- Long serial = mUserToSerialMap.get(user);
- return serial == null ? 0 : serial;
- }
- }
- return mUserManager.getSerialNumberForUser(user);
+ Long serial = mUserToSerialMap.get(user);
+ return serial == null ? 0 : serial;
}
/**
* @see UserManager#getUserForSerialNumber(long)
*/
public UserHandle getUserForSerialNumber(long serialNumber) {
- synchronized (this) {
- if (mUsers != null) {
- return mUsers.get(serialNumber);
- }
- }
- return mUserManager.getUserForSerialNumber(serialNumber);
+ Long value = serialNumber;
+ return mUserToSerialMap
+ .entrySet()
+ .stream()
+ .filter(entry -> value.equals(entry.getValue()))
+ .findFirst()
+ .map(Map.Entry::getKey)
+ .orElse(Process.myUserHandle());
}
/**
* @see UserManager#getUserProfiles()
*/
public List<UserHandle> getUserProfiles() {
- StringBuilder usersToReturn = new StringBuilder();
- List<UserHandle> users;
- if (sDebugTracing) {
- users = mUserManager.getUserProfiles();
- for (UserHandle u : users) {
- usersToReturn.append(u).append(" && ");
- }
- testLogD(WORK_TAB_MISSING, "users from userManager: " + usersToReturn);
- }
+ return List.copyOf(mUserToSerialMap.keySet());
+ }
- synchronized (this) {
- if (mUsers != null) {
- usersToReturn = new StringBuilder();
- for (UserHandle u : mUserToSerialMap.keySet()) {
- usersToReturn.append(u).append(" && ");
- }
- testLogD(WORK_TAB_MISSING, "users from cache: " + usersToReturn);
- return new ArrayList<>(mUserToSerialMap.keySet());
- } else {
- testLogD(WORK_TAB_MISSING, "users from cache null");
+ private static Map<UserHandle, Long> queryAllUsers(UserManager userManager) {
+ Map<UserHandle, Long> users = new ArrayMap<>();
+ List<UserHandle> usersActual = userManager.getUserProfiles();
+ if (usersActual != null) {
+ for (UserHandle user : usersActual) {
+ long serial = userManager.getSerialNumberForUser(user);
+ users.put(user, serial);
}
}
-
- users = mUserManager.getUserProfiles();
- return users == null ? Collections.emptyList() : users;
+ return users;
}
}
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 72f99cb..e0f245f 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -18,11 +18,11 @@
import static androidx.core.content.ContextCompat.getColorStateList;
-import static com.android.launcher3.anim.Interpolators.ACCELERATED_EASE;
-import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.ACCELERATED_EASE;
+import static com.android.app.animation.Interpolators.DECELERATED_EASE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_MATERIAL_U_POPUP;
import android.animation.Animator;
@@ -673,10 +673,6 @@
}
mIsOpen = false;
- mOpenCloseAnimator = getOpenCloseAnimator(false, mCloseDuration, mCloseFadeStartDelay,
- mCloseFadeDuration, mCloseChildFadeStartDelay, mCloseChildFadeDuration,
- ACCELERATED_EASE);
-
mOpenCloseAnimator = ENABLE_MATERIAL_U_POPUP.get()
? getMaterialUOpenCloseAnimator(
false,
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 7c9ab87..eab0969 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -16,10 +16,12 @@
package com.android.launcher3.popup;
+import static com.android.launcher3.Utilities.allowBGLaunch;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.TargetApi;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
@@ -84,6 +86,8 @@
final WeakReference<BaseDraggingActivity> weakTarget = new WeakReference<>(mTarget);
final String actionIdentity = mAction.getTitle() + ", "
+ mItemInfo.getTargetComponent().getPackageName();
+
+ ActivityOptions options = allowBGLaunch(ActivityOptions.makeBasic());
try {
if (DEBUG) Log.d(TAG, "Sending action: " + actionIdentity);
mAction.getActionIntent().send(
@@ -103,7 +107,9 @@
}
}
},
- MAIN_EXECUTOR.getHandler());
+ MAIN_EXECUTOR.getHandler(),
+ null,
+ options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Remote action canceled: " + actionIdentity, e);
Toast.makeText(mTarget, mTarget.getString(
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index c718dcc..575551b 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -28,7 +28,6 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
-import android.os.Binder;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserManager;
@@ -50,6 +49,13 @@
*/
public class LauncherDbUtils {
+ /**
+ * Returns a string which can be used as a where clause for DB query to match the given itemId
+ */
+ public static String itemIdMatch(int itemId) {
+ return "_id=" + itemId;
+ }
+
public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName,
String columnName, String selection, String groupBy, String orderBy) {
IntArray out = new IntArray();
@@ -114,6 +120,10 @@
deletedShortcuts.add(lc.id);
continue;
}
+ if (TextUtils.isEmpty(lc.getTitle())) {
+ deletedShortcuts.add(lc.id);
+ continue;
+ }
// Make sure the target intent can be launched without any permissions. Otherwise remove
// the shortcut
@@ -166,7 +176,7 @@
/**
* Utility class to simplify managing sqlite transactions
*/
- public static class SQLiteTransaction extends Binder implements AutoCloseable {
+ public static class SQLiteTransaction implements AutoCloseable {
private final SQLiteDatabase mDb;
public SQLiteTransaction(SQLiteDatabase db) {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index c554def..4725dd1 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -82,11 +82,15 @@
public static final String APPWIDGET_OLD_IDS = "appwidget_old_ids";
public static final String APPWIDGET_IDS = "appwidget_ids";
+ private static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
+ "container", "cellX", "cellY", "spanX", "spanY", "intent"};
+
/**
* Tries to restore the backup DB if needed
*/
public static void restoreIfNeeded(Context context, ModelDbController dbController) {
if (!isPending(context)) {
+ Log.d(TAG, "No restore task pending, exiting RestoreDbTask");
return;
}
if (!performRestore(context, dbController)) {
@@ -106,6 +110,7 @@
private static boolean performRestore(Context context, ModelDbController controller) {
SQLiteDatabase db = controller.getDb();
+ FileLog.d(TAG, "performRestore: starting restore from db");
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
RestoreDbTask task = new RestoreDbTask();
task.sanitizeDB(context, controller, db, new BackupManager(context));
@@ -133,10 +138,11 @@
@VisibleForTesting
protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db,
BackupManager backupManager) throws Exception {
+ FileLog.d(TAG, "Old Launcher Database before sanitizing:");
// Primary user ids
long myProfileId = controller.getSerialNumberForUser(myUserHandle());
long oldProfileId = getDefaultProfileId(db);
- Log.d(TAG, "sanitizeDB: myProfileId=" + myProfileId + " oldProfileId=" + oldProfileId);
+ FileLog.d(TAG, "sanitizeDB: myProfileId=" + myProfileId + " oldProfileId=" + oldProfileId);
LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
+ 1);
@@ -149,8 +155,11 @@
if (user != null) {
long newManagedProfileId = controller.getSerialNumberForUser(user);
profileMapping.put(oldManagedProfileId, newManagedProfileId);
- Log.d(TAG, "sanitizeDB: managed profile id=" + oldManagedProfileId
+ FileLog.d(TAG, "sanitizeDB: managed profile id=" + oldManagedProfileId
+ " should be mapped to new id=" + newManagedProfileId);
+ } else {
+ FileLog.e(TAG, "sanitizeDB: No User found for old profileId, Ancestral Serial "
+ + "Number: " + oldManagedProfileId);
}
}
@@ -161,11 +170,13 @@
for (int i = numProfiles - 1; i >= 1; --i) {
profileIds[i] = Long.toString(profileMapping.keyAt(i));
}
+
final String[] args = new String[profileIds.length];
Arrays.fill(args, "?");
final String where = "profileId NOT IN (" + TextUtils.join(", ", Arrays.asList(args)) + ")";
- int itemsDeleted = db.delete(Favorites.TABLE_NAME, where, profileIds);
- FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
+ logUnrestoredItems(db, where, profileIds);
+ int itemsDeletedCount = db.delete(Favorites.TABLE_NAME, where, profileIds);
+ FileLog.d(TAG, itemsDeletedCount + " total items from unrestored user(s) were deleted");
// Mark all items as restored.
boolean keepAllIcons = Utilities.isPropertyEnabled(LogConfig.KEEP_ALL_ICONS);
@@ -219,8 +230,48 @@
// Override shortcuts
maybeOverrideShortcuts(context, controller, db, myProfileId);
+ return itemsDeletedCount;
+ }
- return itemsDeleted;
+ /**
+ * Queries and logs the items we will delete from unrestored profiles in the launcher db.
+ * This is to understand why items might be missing during the restore process for Launcher.
+ * @param database the Launcher db to query from.
+ * @param where the SELECT statement to query items that will be deleted.
+ * @param profileIds the profile ID's the user will be migrating to.
+ */
+ private void logUnrestoredItems(SQLiteDatabase database, String where, String[] profileIds) {
+ try (Cursor itemsToDelete = database.query(
+ /* table */ Favorites.TABLE_NAME,
+ /* columns */ DB_COLUMNS_TO_LOG,
+ /* selection */ where,
+ /* selection args */ profileIds,
+ /* groupBy */ null,
+ /* having */ null,
+ /* orderBy */ null
+ )) {
+ if (itemsToDelete.moveToFirst()) {
+ String[] columnNames = itemsToDelete.getColumnNames();
+ StringBuilder stringBuilder = new StringBuilder(
+ "items to be deleted from the Favorites Table during restore:\n"
+ );
+ do {
+ for (String columnName : columnNames) {
+ stringBuilder.append(columnName)
+ .append("=")
+ .append(itemsToDelete.getString(
+ itemsToDelete.getColumnIndex(columnName)))
+ .append(" ");
+ }
+ stringBuilder.append("\n");
+ } while (itemsToDelete.moveToNext());
+ FileLog.d(TAG, stringBuilder.toString());
+ } else {
+ FileLog.d(TAG, "logDeletedItems: No items found to delete");
+ }
+ } catch (Exception e) {
+ FileLog.e(TAG, "logDeletedItems: Error reading from database", e);
+ }
}
/**
@@ -321,7 +372,7 @@
* Marks the DB state as pending restoration
*/
public static void setPending(Context context) {
- FileLog.d(TAG, "Restore data received through full backup ");
+ FileLog.d(TAG, "Restore data received through full backup");
LauncherPrefs.get(context)
.putSync(RESTORE_DEVICE.to(new DeviceGridState(context).getDeviceType()));
}
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
new file mode 100644
index 0000000..3c59c1d
--- /dev/null
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.recyclerview
+
+import android.content.Context
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.allapps.BaseAllAppsAdapter
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
+import com.android.launcher3.views.ActivityContext
+import java.util.concurrent.Future
+
+const val PREINFLATE_ICONS_ROW_COUNT = 4
+const val EXTRA_ICONS_COUNT = 2
+
+/**
+ * An [RecycledViewPool] that preinflates app icons ([ViewHolder] of [BubbleTextView]) of all apps
+ * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
+ * will be added to [RecycledViewPool] on main thread.
+ */
+class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
+
+ private var future: Future<Void>? = null
+
+ /**
+ * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
+ */
+ fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
+ val appsView = context.appsView ?: return
+ val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
+ val preInflateCount = getPreinflateCount(context)
+ if (preInflateCount <= 0) {
+ return
+ }
+
+ // Because we perform onCreateViewHolder() on worker thread, we need a separate
+ // adapter/inflator object as they are not thread-safe. Note that the adapter
+ // just need to perform onCreateViewHolder(parent, VIEW_TYPE_ICON) so it doesn't need
+ // data source information.
+ val adapter: RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> =
+ object : BaseAllAppsAdapter<T>(context, context.appsView.layoutInflater, null, null) {
+ override fun setAppsPerRow(appsPerRow: Int) = Unit
+ override fun getLayoutManager(): RecyclerView.LayoutManager? = null
+ }
+
+ // Inflate view holders on background thread, and added to view pool on main thread.
+ future?.cancel(true)
+ future =
+ VIEW_PREINFLATION_EXECUTOR.submit<Void> {
+ val viewHolders =
+ Array(preInflateCount) {
+ adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
+ }
+ MAIN_EXECUTOR.execute {
+ for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
+ putRecycledView(viewHolders[i])
+ }
+ }
+ null
+ }
+ }
+
+ /**
+ * After testing on phone, foldable and tablet, we found [PREINFLATE_ICONS_ROW_COUNT] rows of
+ * app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to
+ * suffice fast scrolling.
+ *
+ * Note that if [FeatureFlags.ALL_APPS_GONE_VISIBILITY] is enabled, we need to preinfate extra
+ * app icons in size of one all apps pages, so that opening all apps don't need to inflate app
+ * icons.
+ */
+ fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
+ var targetPreinflateCount =
+ PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
+ EXTRA_ICONS_COUNT
+ if (FeatureFlags.ALL_APPS_GONE_VISIBILITY.get()) {
+ val grid = ActivityContext.lookupContext<T>(context).deviceProfile
+ val approxRows =
+ Math.ceil((grid.availableHeightPx / grid.allAppsIconSizePx).toDouble()).toInt()
+ targetPreinflateCount += (approxRows + 1) * grid.numShownAllAppsColumns
+ }
+ val existingPreinflateCount = getRecycledViewCount(BaseAllAppsAdapter.VIEW_TYPE_ICON)
+ return targetPreinflateCount - existingPreinflateCount
+ }
+}
diff --git a/src/com/android/launcher3/responsive/AllAppsSpecs.kt b/src/com/android/launcher3/responsive/AllAppsSpecs.kt
new file mode 100644
index 0000000..8ed3ffc
--- /dev/null
+++ b/src/com/android/launcher3/responsive/AllAppsSpecs.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.SpecType
+import com.android.launcher3.util.ResourceHelper
+
+class AllAppsSpecs(widthSpecs: List<AllAppsSpec>, heightSpecs: List<AllAppsSpec>) :
+ ResponsiveSpecs<AllAppsSpec>(widthSpecs, heightSpecs) {
+
+ fun getCalculatedWidthSpec(
+ columns: Int,
+ availableWidth: Int,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+ ): CalculatedAllAppsSpec {
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
+ "Invalid specType for CalculatedWorkspaceSpec. " +
+ "Expected: ${SpecType.WIDTH} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
+ }
+
+ val spec = getWidthSpec(availableWidth)
+ return CalculatedAllAppsSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
+ }
+
+ fun getCalculatedHeightSpec(
+ rows: Int,
+ availableHeight: Int,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+ ): CalculatedAllAppsSpec {
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
+ "Invalid specType for CalculatedWorkspaceSpec. " +
+ "Expected: ${SpecType.HEIGHT} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
+ }
+
+ val spec = getHeightSpec(availableHeight)
+ return CalculatedAllAppsSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
+ }
+
+ companion object {
+ private const val XML_ALL_APPS_SPEC = "allAppsSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): AllAppsSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_ALL_APPS_SPEC, ::AllAppsSpec)
+ val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
+ return AllAppsSpecs(widthSpecs, heightSpecs)
+ }
+ }
+}
+
+data class AllAppsSpec(
+ override val maxAvailableSize: Int,
+ override val specType: SpecType,
+ override val startPadding: SizeSpec,
+ override val endPadding: SizeSpec,
+ override val gutter: SizeSpec,
+ override val cellSize: SizeSpec
+) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
+
+ init {
+ check(isValid()) { "Invalid AllAppsSpec found." }
+ }
+
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ SpecType.values()[
+ attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
+ startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
+ endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
+ gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
+ cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
+ )
+}
+
+class CalculatedAllAppsSpec(
+ availableSpace: Int,
+ cells: Int,
+ spec: AllAppsSpec,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/FolderSpecs.kt b/src/com/android/launcher3/responsive/FolderSpecs.kt
new file mode 100644
index 0000000..bc2db28
--- /dev/null
+++ b/src/com/android/launcher3/responsive/FolderSpecs.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.SpecType
+import com.android.launcher3.util.ResourceHelper
+
+class FolderSpecs(widthSpecs: List<FolderSpec>, heightSpecs: List<FolderSpec>) :
+ ResponsiveSpecs<FolderSpec>(widthSpecs, heightSpecs) {
+
+ fun getCalculatedWidthSpec(
+ columns: Int,
+ availableWidth: Int,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+ ): CalculatedFolderSpec {
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
+ "Invalid specType for CalculatedWorkspaceSpec. " +
+ "Expected: ${SpecType.WIDTH} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
+ }
+
+ val spec = getWidthSpec(availableWidth)
+ return CalculatedFolderSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
+ }
+
+ fun getCalculatedHeightSpec(
+ rows: Int,
+ availableHeight: Int,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+ ): CalculatedFolderSpec {
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
+ "Invalid specType for CalculatedWorkspaceSpec. " +
+ "Expected: ${SpecType.HEIGHT} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
+ }
+
+ val spec = getHeightSpec(availableHeight)
+ return CalculatedFolderSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
+ }
+
+ companion object {
+
+ private const val XML_FOLDER_SPEC = "folderSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): FolderSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_FOLDER_SPEC, ::FolderSpec)
+ val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
+ return FolderSpecs(widthSpecs, heightSpecs)
+ }
+ }
+}
+
+data class FolderSpec(
+ override val maxAvailableSize: Int,
+ override val specType: SpecType,
+ override val startPadding: SizeSpec,
+ override val endPadding: SizeSpec,
+ override val gutter: SizeSpec,
+ override val cellSize: SizeSpec
+) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
+
+ init {
+ check(isValid()) { "Invalid FolderSpec found." }
+ }
+
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ SpecType.values()[
+ attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
+ startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
+ endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
+ gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
+ cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
+ )
+}
+
+class CalculatedFolderSpec(
+ availableSpace: Int,
+ cells: Int,
+ spec: FolderSpec,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/HotseatSpecs.kt b/src/com/android/launcher3/responsive/HotseatSpecs.kt
new file mode 100644
index 0000000..482508d
--- /dev/null
+++ b/src/com/android/launcher3/responsive/HotseatSpecs.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import android.util.Log
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+
+class HotseatSpecs(val specs: List<HotseatSpec>) {
+
+ fun getCalculatedHeightSpec(availableHeight: Int): CalculatedHotseatSpec {
+ val spec = specs.firstOrNull { availableHeight <= it.maxAvailableSize }
+ check(spec != null) { "No available height spec found within $availableHeight." }
+ return CalculatedHotseatSpec(availableHeight, spec)
+ }
+
+ companion object {
+ private const val XML_HOTSEAT_SPEC = "hotseatSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): HotseatSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_HOTSEAT_SPEC, ::HotseatSpec)
+ return HotseatSpecs(specs.filter { it.specType == ResponsiveSpec.SpecType.HEIGHT })
+ }
+ }
+}
+
+data class HotseatSpec(
+ val maxAvailableSize: Int,
+ val specType: ResponsiveSpec.SpecType,
+ val hotseatQsbSpace: SizeSpec
+) {
+
+ init {
+ check(isValid()) { "Invalid HotseatSpec found." }
+ }
+
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ ResponsiveSpec.SpecType.values()[
+ attrs.getInt(
+ R.styleable.ResponsiveSpec_specType,
+ ResponsiveSpec.SpecType.HEIGHT.ordinal
+ )],
+ hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE)
+ )
+
+ fun isValid(): Boolean {
+ if (maxAvailableSize <= 0) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+ return false
+ }
+
+ // All specs need to be individually valid
+ if (!allSpecsAreValid()) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+ return false
+ }
+
+ return true
+ }
+
+ private fun allSpecsAreValid(): Boolean {
+ return hotseatQsbSpace.isValid() && hotseatQsbSpace.onlyFixedSize()
+ }
+
+ companion object {
+ private const val LOG_TAG = "HotseatSpec"
+ }
+}
+
+class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) {
+
+ var hotseatQsbSpace: Int = 0
+ private set
+
+ init {
+ hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace)
+ }
+
+ override fun hashCode(): Int {
+ var result = availableSpace.hashCode()
+ result = 31 * result + hotseatQsbSpace.hashCode()
+ result = 31 * result + spec.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is CalculatedHotseatSpec &&
+ availableSpace == other.availableSpace &&
+ hotseatQsbSpace == other.hotseatQsbSpace &&
+ spec == other.spec
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}(" +
+ "availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " +
+ "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+ ")"
+ }
+}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecs.kt b/src/com/android/launcher3/responsive/ResponsiveSpecs.kt
new file mode 100644
index 0000000..72a0ea4
--- /dev/null
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecs.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.responsive
+
+import android.util.Log
+
+/**
+ * Base class for responsive specs that holds a list of width and height specs.
+ *
+ * @param widthSpecs List of width responsive specifications
+ * @param heightSpecs List of height responsive specifications
+ */
+abstract class ResponsiveSpecs<T : ResponsiveSpec>(
+ val widthSpecs: List<T>,
+ val heightSpecs: List<T>
+) {
+
+ init {
+ check(widthSpecs.isNotEmpty() && heightSpecs.isNotEmpty()) {
+ "${this::class.simpleName} is incomplete - " +
+ "width list size = ${widthSpecs.size}; " +
+ "height list size = ${heightSpecs.size}."
+ }
+ }
+
+ /**
+ * Get a [ResponsiveSpec] for width within the breakpoint.
+ *
+ * @param availableWidth The width breakpoint for the spec
+ * @return A [ResponsiveSpec] for width.
+ */
+ fun getWidthSpec(availableWidth: Int): T {
+ val spec = widthSpecs.firstOrNull { availableWidth <= it.maxAvailableSize }
+ check(spec != null) { "No available width spec found within $availableWidth." }
+ return spec
+ }
+
+ /**
+ * Get a [ResponsiveSpec] for height within the breakpoint.
+ *
+ * @param availableHeight The height breakpoint for the spec
+ * @return A [ResponsiveSpec] for height.
+ */
+ fun getHeightSpec(availableHeight: Int): T {
+ val spec = heightSpecs.firstOrNull { availableHeight <= it.maxAvailableSize }
+ check(spec != null) { "No available height spec found within $availableHeight." }
+ return spec
+ }
+}
+
+/**
+ * Base class for a responsive specification that is used to calculate the paddings, gutter and cell
+ * size.
+ *
+ * @param maxAvailableSize indicates the breakpoint to use this specification.
+ * @param specType indicates whether the paddings and gutters will be applied vertically or
+ * horizontally.
+ * @param startPadding padding used at the top or left (right in RTL) in the workspace folder.
+ * @param endPadding padding used at the bottom or right (left in RTL) in the workspace folder.
+ * @param gutter the space between the cells vertically or horizontally depending on the [specType].
+ * @param cellSize height or width of the cell depending on the [specType].
+ */
+abstract class ResponsiveSpec(
+ open val maxAvailableSize: Int,
+ open val specType: SpecType,
+ open val startPadding: SizeSpec,
+ open val endPadding: SizeSpec,
+ open val gutter: SizeSpec,
+ open val cellSize: SizeSpec
+) {
+ open fun isValid(): Boolean {
+ if (maxAvailableSize <= 0) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+ return false
+ }
+
+ // All specs need to be individually valid
+ if (!allSpecsAreValid()) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+ return false
+ }
+
+ return true
+ }
+
+ private fun allSpecsAreValid(): Boolean {
+ return startPadding.isValid() &&
+ endPadding.isValid() &&
+ gutter.isValid() &&
+ cellSize.isValid()
+ }
+
+ enum class SpecType {
+ HEIGHT,
+ WIDTH
+ }
+
+ companion object {
+ private const val LOG_TAG = "ResponsiveSpec"
+ }
+}
+
+/**
+ * Calculated responsive specs contains the final paddings, gutter and cell size in pixels after
+ * they are calculated from the available space, cells and workspace specs.
+ */
+sealed class CalculatedResponsiveSpec {
+ var availableSpace: Int = 0
+ private set
+
+ var cells: Int = 0
+ private set
+
+ var startPaddingPx: Int = 0
+ private set
+
+ var endPaddingPx: Int = 0
+ private set
+
+ var gutterPx: Int = 0
+ private set
+
+ var cellSizePx: Int = 0
+ private set
+
+ var spec: ResponsiveSpec
+ private set
+
+ constructor(
+ availableSpace: Int,
+ cells: Int,
+ spec: ResponsiveSpec,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+ ) {
+ this.availableSpace = availableSpace
+ this.cells = cells
+ this.spec = spec
+
+ // Map if is fixedSize, ofAvailableSpace or matchWorkspace
+ startPaddingPx =
+ spec.startPadding.getCalculatedValue(
+ availableSpace,
+ calculatedWorkspaceSpec.startPaddingPx
+ )
+ endPaddingPx =
+ spec.endPadding.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.endPaddingPx)
+ gutterPx = spec.gutter.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.gutterPx)
+ cellSizePx =
+ spec.cellSize.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.cellSizePx)
+
+ updateRemainderSpaces(availableSpace, cells, spec)
+ }
+
+ constructor(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
+ this.availableSpace = availableSpace
+ this.cells = cells
+ this.spec = spec
+
+ // Map if is fixedSize or ofAvailableSpace
+ startPaddingPx = spec.startPadding.getCalculatedValue(availableSpace)
+ endPaddingPx = spec.endPadding.getCalculatedValue(availableSpace)
+ gutterPx = spec.gutter.getCalculatedValue(availableSpace)
+ cellSizePx = spec.cellSize.getCalculatedValue(availableSpace)
+
+ updateRemainderSpaces(availableSpace, cells, spec)
+ }
+
+ private fun updateRemainderSpaces(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
+ val gutters = cells - 1
+ val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
+ val remainderSpace = availableSpace - usedSpace
+
+ startPaddingPx = spec.startPadding.getRemainderSpaceValue(remainderSpace, startPaddingPx)
+ endPaddingPx = spec.endPadding.getRemainderSpaceValue(remainderSpace, endPaddingPx)
+ gutterPx = spec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx)
+ cellSizePx = spec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx)
+ }
+
+ override fun hashCode(): Int {
+ var result = availableSpace.hashCode()
+ result = 31 * result + cells.hashCode()
+ result = 31 * result + startPaddingPx.hashCode()
+ result = 31 * result + endPaddingPx.hashCode()
+ result = 31 * result + gutterPx.hashCode()
+ result = 31 * result + cellSizePx.hashCode()
+ result = 31 * result + spec.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is CalculatedResponsiveSpec &&
+ availableSpace == other.availableSpace &&
+ cells == other.cells &&
+ startPaddingPx == other.startPaddingPx &&
+ endPaddingPx == other.endPaddingPx &&
+ gutterPx == other.gutterPx &&
+ cellSizePx == other.cellSizePx &&
+ spec == other.spec
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}(" +
+ "availableSpace=$availableSpace, cells=$cells, startPaddingPx=$startPaddingPx, " +
+ "endPaddingPx=$endPaddingPx, gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
+ "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+ ")"
+ }
+}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt b/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt
new file mode 100644
index 0000000..a89b619
--- /dev/null
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import android.content.res.XmlResourceParser
+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
+
+class ResponsiveSpecsParser(private val resourceHelper: ResourceHelper) {
+
+ private fun parseSizeSpecs(parser: XmlResourceParser): Map<String, SizeSpec> {
+ val parentName = parser.name
+ parser.next()
+
+ val result = mutableMapOf<String, SizeSpec>()
+ while (parser.eventType != XmlPullParser.END_DOCUMENT && parser.name != parentName) {
+ if (parser.eventType == XmlResourceParser.START_TAG) {
+ result[parser.name] = SizeSpec.create(resourceHelper, Xml.asAttributeSet(parser))
+ }
+ parser.next()
+ }
+
+ return result
+ }
+
+ fun <T> parseXML(
+ tagName: String,
+ map: (attributes: TypedArray, sizeSpecs: Map<String, SizeSpec>) -> T
+ ): List<T> {
+ val parser: XmlResourceParser = resourceHelper.getXml()
+
+ try {
+ val list = mutableListOf<T>()
+
+ var eventType = parser.eventType
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlResourceParser.START_TAG && parser.name == tagName) {
+ val attrs =
+ resourceHelper.obtainStyledAttributes(
+ Xml.asAttributeSet(parser),
+ R.styleable.ResponsiveSpec
+ )
+
+ val sizeSpecs = parseSizeSpecs(parser)
+ list += map(attrs, sizeSpecs)
+ attrs.recycle()
+ }
+
+ eventType = parser.next()
+ }
+
+ parser.close()
+
+ return list
+ } catch (e: Exception) {
+ when (e) {
+ is NoSuchFieldException,
+ is IOException,
+ is XmlPullParserException ->
+ throw RuntimeException("Failure parsing specs file.", e)
+ else -> throw e
+ }
+ } finally {
+ parser.close()
+ }
+ }
+}
+
+fun Map<String, SizeSpec>.getOrError(key: String): SizeSpec {
+ return this.getOrElse(key) { error("Attr '$key' must be defined.") }
+}
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
new file mode 100644
index 0000000..c868c9f
--- /dev/null
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.util.Log
+import android.util.TypedValue
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+import kotlin.math.roundToInt
+
+/**
+ * [SizeSpec] is an attribute used to represent a property in the responsive grid specs.
+ *
+ * @param fixedSize a fixed size in dp to be used
+ * @param ofAvailableSpace a percentage of the available space
+ * @param ofRemainderSpace a percentage of the remaining space (available space minus used space)
+ * @param matchWorkspace indicates whether the workspace value will be used or not.
+ * @param maxSize restricts the maximum value allowed for the [SizeSpec].
+ */
+data class SizeSpec(
+ val fixedSize: Float = 0f,
+ val ofAvailableSpace: Float = 0f,
+ val ofRemainderSpace: Float = 0f,
+ val matchWorkspace: Boolean = false,
+ val maxSize: Int = Int.MAX_VALUE
+) {
+
+ /** Retrieves the correct value for [SizeSpec]. */
+ fun getCalculatedValue(availableSpace: Int, workspaceValue: Int = 0): Int {
+ val calculatedValue =
+ when {
+ fixedSize > 0 -> fixedSize.roundToInt()
+ matchWorkspace -> workspaceValue
+ ofAvailableSpace > 0 -> (ofAvailableSpace * availableSpace).roundToInt()
+ else -> 0
+ }
+
+ return calculatedValue.coerceAtMost(maxSize)
+ }
+
+ /**
+ * Calculates the [SizeSpec] value when remainder space value is defined. If no remainderSpace
+ * is 0, returns a default value.
+ */
+ fun getRemainderSpaceValue(remainderSpace: Int, defaultValue: Int): Int {
+ val remainderSpaceValue =
+ if (ofRemainderSpace > 0) {
+ (ofRemainderSpace * remainderSpace).roundToInt()
+ } else {
+ defaultValue
+ }
+
+ return remainderSpaceValue.coerceAtMost(maxSize)
+ }
+
+ fun isValid(): Boolean {
+ // All attributes are empty
+ if (fixedSize < 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f && !matchWorkspace) {
+ 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 (matchWorkspace) 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
+ }
+
+ // Invalid fixed or max size
+ if (fixedSize < 0f || maxSize < 0f) {
+ Log.e(TAG, "SizeSpec#isValid - values should be bigger or equal to zero.")
+ return false
+ }
+
+ if (fixedSize > 0f && fixedSize > maxSize) {
+ Log.e(TAG, "SizeSpec#isValid - fixed size should be smaller than the max size.")
+ return false
+ }
+
+ return true
+ }
+
+ fun onlyFixedSize(): Boolean {
+ if (ofAvailableSpace > 0 || ofRemainderSpace > 0 || matchWorkspace) {
+ Log.e(TAG, "SizeSpec#onlyFixedSize - only fixed size allowed for this tag")
+ return false
+ }
+ return true
+ }
+
+ object XmlTags {
+ const val START_PADDING = "startPadding"
+ const val END_PADDING = "endPadding"
+ const val GUTTER = "gutter"
+ const val CELL_SIZE = "cellSize"
+ const val HOTSEAT_QSB_SPACE = "hotseatQsbSpace"
+ }
+
+ companion object {
+ private const val TAG = "SizeSpec"
+
+ private fun getValue(a: TypedArray, index: Int): Float {
+ return when (a.getType(index)) {
+ TypedValue.TYPE_DIMENSION -> a.getDimensionPixelSize(index, 0).toFloat()
+ TypedValue.TYPE_FLOAT -> a.getFloat(index, 0f)
+ else -> 0f
+ }
+ }
+
+ fun create(resourceHelper: ResourceHelper, attrs: AttributeSet): SizeSpec {
+ val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SizeSpec)
+
+ val fixedSize = getValue(styledAttrs, R.styleable.SizeSpec_fixedSize)
+ val ofAvailableSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofAvailableSpace)
+ val ofRemainderSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofRemainderSpace)
+ val matchWorkspace = styledAttrs.getBoolean(R.styleable.SizeSpec_matchWorkspace, false)
+ val maxSize =
+ styledAttrs.getDimensionPixelSize(R.styleable.SizeSpec_maxSize, Int.MAX_VALUE)
+
+ styledAttrs.recycle()
+
+ return SizeSpec(fixedSize, ofAvailableSpace, ofRemainderSpace, matchWorkspace, maxSize)
+ }
+ }
+}
diff --git a/src/com/android/launcher3/responsive/WorkspaceSpecs.kt b/src/com/android/launcher3/responsive/WorkspaceSpecs.kt
new file mode 100644
index 0000000..0da7026
--- /dev/null
+++ b/src/com/android/launcher3/responsive/WorkspaceSpecs.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import android.util.Log
+import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.SpecType
+import com.android.launcher3.util.ResourceHelper
+
+private const val TAG = "WorkspaceSpecs"
+
+class WorkspaceSpecs(widthSpecs: List<WorkspaceSpec>, heightSpecs: List<WorkspaceSpec>) :
+ ResponsiveSpecs<WorkspaceSpec>(widthSpecs, heightSpecs) {
+
+ fun getCalculatedWidthSpec(columns: Int, availableWidth: Int): CalculatedWorkspaceSpec {
+ val spec = getWidthSpec(availableWidth)
+ return CalculatedWorkspaceSpec(availableWidth, columns, spec)
+ }
+
+ fun getCalculatedHeightSpec(rows: Int, availableHeight: Int): CalculatedWorkspaceSpec {
+ val spec = getHeightSpec(availableHeight)
+ return CalculatedWorkspaceSpec(availableHeight, rows, spec)
+ }
+
+ companion object {
+ private const val XML_WORKSPACE_SPEC = "workspaceSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): WorkspaceSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_WORKSPACE_SPEC, ::WorkspaceSpec)
+ val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
+ return WorkspaceSpecs(widthSpecs, heightSpecs)
+ }
+ }
+}
+
+data class WorkspaceSpec(
+ override val maxAvailableSize: Int,
+ override val specType: SpecType,
+ override val startPadding: SizeSpec,
+ override val endPadding: SizeSpec,
+ override val gutter: SizeSpec,
+ override val cellSize: SizeSpec
+) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
+
+ init {
+ check(isValid()) { "Invalid WorkspaceSpec found." }
+ }
+
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ SpecType.values()[
+ attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
+ startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
+ endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
+ gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
+ cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
+ )
+
+ override fun isValid(): Boolean {
+ // Workspace spec should not match workspace
+ if (
+ startPadding.matchWorkspace ||
+ endPadding.matchWorkspace ||
+ gutter.matchWorkspace ||
+ cellSize.matchWorkspace
+ ) {
+ Log.e(TAG, "WorkspaceSpec#isValid - workspace shouldn't contain matchWorkspace!")
+ return false
+ }
+
+ return super.isValid()
+ }
+}
+
+class CalculatedWorkspaceSpec(availableSpace: Int, cells: Int, spec: WorkspaceSpec) :
+ CalculatedResponsiveSpec(availableSpace, cells, spec)
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index f03c62a..2d69bfa 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -60,13 +60,15 @@
private final OnClickListener mOnClickListener;
private final OnLongClickListener mOnLongClickListener;
private final SharedPreferences mPrefs;
- private final AllAppsStore mAllAppsList;
+ private final AllAppsStore<SecondaryDisplayLauncher> mAllAppsList;
private final AppInfoComparator mAppNameComparator;
private final Set<ComponentKey> mPinnedApps = new HashSet<>();
private final ArrayList<AppInfo> mItems = new ArrayList<>();
- public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+ public PinnedAppsAdapter(
+ SecondaryDisplayLauncher launcher,
+ AllAppsStore<SecondaryDisplayLauncher> allAppsStore,
OnLongClickListener onLongClickListener) {
mLauncher = launcher;
mOnClickListener = launcher.getItemOnClickListener();
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 458f137..a10c0ad 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -72,7 +72,7 @@
implements BgDataModel.Callbacks, DragController.DragListener {
private LauncherModel mModel;
- private BaseDragLayer mDragLayer;
+ private SecondaryDragLayer mDragLayer;
private SecondaryDragController mDragController;
private ActivityAllAppsContainerView<SecondaryDisplayLauncher> mAppsView;
private View mAppsButton;
@@ -302,7 +302,7 @@
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
- AllAppsStore appsStore = mAppsView.getAppsStore();
+ AllAppsStore<SecondaryDisplayLauncher> appsStore = mAppsView.getAppsStore();
appsStore.setApps(apps, flags, packageUserKeytoUidMap);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
@@ -314,10 +314,6 @@
}
}
- public SecondaryDisplayPredictions getSecondaryDisplayPredictions() {
- return mSecondaryDisplayPredictions;
- }
-
@Override
public StringCache getStringCache() {
return mStringCache;
@@ -337,6 +333,11 @@
return this::onIconClicked;
}
+ @Override
+ public View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return v -> mDragLayer.onIconLongClicked(v);
+ }
+
private void onIconClicked(View v) {
// Make sure that rogue clicks don't get through while allapps is launching, or after the
// view has detached (it's possible for this to happen if the view is removed mid touch).
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
index 21c50d3..a58916a 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
@@ -16,10 +16,8 @@
package com.android.launcher3.secondarydisplay;
import android.content.Context;
-import android.view.View;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -47,12 +45,4 @@
*/
public void setPredictedApps(BgDataModel.FixedContainerItems item) {
}
-
- /**
- * Set long click listener for predicted apps in top of app drawer.
- */
- public void setLongClickListener(
- ActivityAllAppsContainerView<?> appsView,
- View.OnLongClickListener onIconLongClickListener) {
- }
}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 87afcab..e8be12c 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -79,9 +79,6 @@
mAllAppsButton = findViewById(R.id.all_apps_button);
mAppsView = findViewById(R.id.apps_view);
- mAppsView.setOnIconLongClickListener(this::onIconLongClicked);
- mActivity.getSecondaryDisplayPredictions()
- .setLongClickListener(mAppsView, this::onIconLongClicked);
// Setup workspace
mWorkspace = findViewById(R.id.workspace_grid);
mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(),
@@ -179,7 +176,7 @@
return mPinnedAppsAdapter;
}
- private boolean onIconLongClicked(View v) {
+ boolean onIconLongClicked(View v) {
if (!(v instanceof BubbleTextView)) {
return false;
}
@@ -243,9 +240,8 @@
public void onPreDragStart(DropTarget.DragObject dragObject) {
mDragView = dragObject.dragView;
if (!shouldStartDrag(0)) {
- mDragView.setOnAnimationEndCallback(() -> {
- mActivity.beginDragShared(v, mActivity.getAppsView(), options);
- });
+ mDragView.setOnScaleAnimEndCallback(() ->
+ mActivity.beginDragShared(v, mActivity.getAppsView(), options));
}
}
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 198dad3..360ff7e 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -27,6 +27,7 @@
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
+import android.util.Log;
import androidx.annotation.FloatRange;
@@ -35,6 +36,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.shared.TestProtocol;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -76,6 +78,10 @@
return mState;
}
+ public STATE_TYPE getTargetState() {
+ return (STATE_TYPE) mConfig.targetState;
+ }
+
public STATE_TYPE getCurrentStableState() {
return mCurrentStableState;
}
@@ -221,6 +227,8 @@
private void goToState(
STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "go to state " + state);
+
animated &= areAnimatorsEnabled();
if (mActivity.isInState(state)) {
if (mConfig.currentAnimation == null) {
@@ -375,6 +383,8 @@
mState = state;
mActivity.onStateSetStart(mState);
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "Notifying listeners for state transition start"
+ + " to state: " + state.toString());
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onStateTransitionStart(state);
}
@@ -392,6 +402,8 @@
setRestState(null);
}
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "Notifying " + mListeners.size() + " listeners "
+ + "for end transition for state: " + state.toString());
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).onStateTransitionComplete(state);
}
@@ -429,6 +441,7 @@
* Cancels the current animation.
*/
public void cancelAnimation() {
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "current animation cancelled");
mConfig.reset();
// It could happen that a new animation is set as a result of an endListener on the
// existing animation.
@@ -452,6 +465,7 @@
* @param toState The state we are animating towards.
*/
public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) {
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "setting animation to " + toState.toString());
cancelAnimation();
setCurrentAnimation(anim);
anim.addListener(createStateAnimationListener(toState));
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index d1e816b..0d9e010 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -66,7 +66,8 @@
ANIM_WORKSPACE_PAGE_TRANSLATE_X,
ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
- ANIM_ALL_APPS_BOTTOM_SHEET_FADE
+ ANIM_ALL_APPS_BOTTOM_SHEET_FADE,
+ ANIM_ALL_APPS_KEYBOARD_FADE
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimType {}
@@ -90,8 +91,9 @@
public static final int ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN = 17;
public static final int ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE = 18;
public static final int ANIM_ALL_APPS_BOTTOM_SHEET_FADE = 19;
+ public static final int ANIM_ALL_APPS_KEYBOARD_FADE = 20;
- private static final int ANIM_TYPES_COUNT = 20;
+ private static final int ANIM_TYPES_COUNT = 21;
protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index e62ccbc..07a1b82 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -16,6 +16,7 @@
package com.android.launcher3.testing;
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -203,10 +204,11 @@
}
case TestProtocol.REQUEST_WORKSPACE_COLUMNS_ROWS: {
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
return getLauncherUIProperty(Bundle::putParcelable, launcher -> new Point(
- InvariantDeviceProfile.INSTANCE.get(mContext).numColumns,
- InvariantDeviceProfile.INSTANCE.get(mContext).numRows)
- );
+ idp.getDeviceProfile(mContext).getPanelCount() * idp.numColumns,
+ idp.numRows
+ ));
}
case TestProtocol.REQUEST_WORKSPACE_CURRENT_PAGE_INDEX: {
@@ -229,7 +231,7 @@
}
case TestProtocol.REQUEST_HAS_TIS: {
- response.putBoolean(TestProtocol.REQUEST_HAS_TIS, false);
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
return response;
}
@@ -245,6 +247,12 @@
+ l.getAppsView().getActiveRecyclerView().getPaddingBottom());
}
+ case TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW: {
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ ENABLE_GRID_ONLY_OVERVIEW.get());
+ return response;
+ }
+
default:
return null;
}
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index f95548d..70691f8 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -27,26 +27,29 @@
import java.util.function.BiConsumer;
public final class TestLogging {
+ private static final String TAPL_EVENTS_TAG = "TaplEvents";
+ private static final String LAUNCHER_EVENTS_TAG = "LauncherEvents";
private static BiConsumer<String, String> sEventConsumer;
public static boolean sHadEventsNotFromTest;
- private static void recordEventSlow(String sequence, String event) {
- Log.d(TestProtocol.TAPL_EVENTS_TAG, sequence + " / " + event);
+ private static void recordEventSlow(String sequence, String event, boolean reportToTapl) {
+ Log.d(reportToTapl ? TAPL_EVENTS_TAG : LAUNCHER_EVENTS_TAG,
+ sequence + " / " + event);
final BiConsumer<String, String> eventConsumer = sEventConsumer;
- if (eventConsumer != null) {
+ if (reportToTapl && eventConsumer != null) {
eventConsumer.accept(sequence, event);
}
}
public static void recordEvent(String sequence, String event) {
if (Utilities.isRunningInTestHarness()) {
- recordEventSlow(sequence, event);
+ recordEventSlow(sequence, event, true);
}
}
public static void recordEvent(String sequence, String message, Object parameter) {
if (Utilities.isRunningInTestHarness()) {
- recordEventSlow(sequence, message + ": " + parameter);
+ recordEventSlow(sequence, message + ": " + parameter, true);
}
}
@@ -59,14 +62,20 @@
public static void recordKeyEvent(String sequence, String message, KeyEvent event) {
if (Utilities.isRunningInTestHarness()) {
- recordEventSlow(sequence, message + ": " + event);
+ recordEventSlow(sequence, message + ": " + event, true);
registerEventNotFromTest(event);
}
}
public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
- if (Utilities.isRunningInTestHarness() && event.getAction() != MotionEvent.ACTION_MOVE) {
- recordEventSlow(sequence, message + ": " + event);
+ final int action = event.getAction();
+ if (Utilities.isRunningInTestHarness() && action != MotionEvent.ACTION_MOVE) {
+ // "Expecting" in TAPL motion events was thought to be producing considerable noise in
+ // tests due to failed checks for expected events. So we are not sending them to TAPL.
+ // Other events, such as EVENT_PILFER_POINTERS produce less noise and are thought to
+ // be more useful.
+ // That's why we pass false as the value for the 'reportToTapl' parameter.
+ recordEventSlow(sequence, message + ": " + event, false);
registerEventNotFromTest(event);
}
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c499e35..cec4574 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.touch;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
@@ -22,7 +23,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
@@ -384,8 +384,8 @@
} else {
logReachedState(targetState);
}
- mLauncher.getRootView().getSysUiScrim().createSysuiMultiplierAnim(
- 1f).setDuration(0).start();
+ mLauncher.getRootView().getSysUiScrim().getSysUIMultiplier().animateToValue(1f)
+ .setDuration(0).start();
}
private void logReachedState(LauncherState targetState) {
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index d028f24..447d22b 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -15,17 +15,20 @@
*/
package com.android.launcher3.touch;
+import static com.android.app.animation.Interpolators.DECELERATED_EASE;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.clampToProgress;
+import static com.android.app.animation.Interpolators.mapToProgress;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_KEYBOARD_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
@@ -40,11 +43,11 @@
import android.view.MotionEvent;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.StateAnimationConfig;
/**
@@ -209,8 +212,8 @@
if (!config.userControlled) {
config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
}
- config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
- config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
+ config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
} else {
if (config.userControlled) {
config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
@@ -250,8 +253,8 @@
if (!config.userControlled) {
config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
}
- config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
- config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
+ config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
} else {
config.setInterpolator(ANIM_DEPTH, config.userControlled ? BLUR_MANUAL : BLUR_ATOMIC);
config.setInterpolator(ANIM_WORKSPACE_FADE,
@@ -293,20 +296,15 @@
config.setInterpolator(ANIM_WORKSPACE_SCALE, INSTANT);
config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, INSTANT);
} else {
- // Remove scrim for this transition.
- config.setInterpolator(ANIM_SCRIM_FADE, progress -> 0);
-
- // For now, pop the background panel in at full opacity at the threshold.
+ // Pop the background panel, keyboard, and content in at full opacity at the threshold.
config.setInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE,
thresholdInterpolator(threshold, INSTANT));
-
- // Fade the apps in when the scrim normally does, so it's apparent sooner what is
- // happening (in this case we are fading them on top of the background panel).
- config.setInterpolator(ANIM_ALL_APPS_FADE,
- thresholdInterpolator(threshold, SCRIM_FADE_MANUAL));
+ config.setInterpolator(ANIM_ALL_APPS_KEYBOARD_FADE,
+ thresholdInterpolator(threshold, INSTANT));
+ config.setInterpolator(ANIM_ALL_APPS_FADE, thresholdInterpolator(threshold, INSTANT));
config.setInterpolator(ANIM_VERTICAL_PROGRESS,
- thresholdInterpolator(threshold, ALL_APPS_VERTICAL_PROGRESS_MANUAL));
+ thresholdInterpolator(threshold, mapToProgress(LINEAR, threshold, 1f)));
}
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 790c226..8c12547 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -42,6 +42,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.logging.InstanceId;
@@ -95,6 +96,8 @@
} else if (tag instanceof FolderInfo) {
if (v instanceof FolderIcon) {
onClickFolderIcon(v);
+ } else if (v instanceof AppPairIcon) {
+ onClickAppPairIcon(v);
}
} else if (tag instanceof AppInfo) {
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
@@ -123,6 +126,17 @@
}
/**
+ * Event handler for an app pair icon click.
+ *
+ * @param v The view that was clicked. Must be an instance of {@link AppPairIcon}.
+ */
+ private static void onClickAppPairIcon(View v) {
+ Launcher launcher = Launcher.getLauncher(v.getContext());
+ FolderInfo folderInfo = ((AppPairIcon) v).getInfo();
+ launcher.launchAppPair(folderInfo.contents.get(0), folderInfo.contents.get(1));
+ }
+
+ /**
* Event handler for the app widget view which has not fully restored.
*/
private static void onClickPendingWidget(PendingAppWidgetHostView v, Launcher launcher) {
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 9cba19d..122b1e0 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -67,7 +67,7 @@
if (!(v.getTag() instanceof ItemInfo)) return false;
launcher.setWaitingForResult(null);
- beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
+ beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
return true;
}
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 6a972eb..dc4621e 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -582,7 +582,8 @@
? splitInfo.dividerHeightPercent
: splitInfo.dividerWidthPercent;
- float scale = (float) outRect.height() / dp.availableHeightPx;
+ int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
+ float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight);
float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
float scaledTopTaskHeight = topTaskHeight * scale;
float dividerHeight = dp.availableHeightPx * dividerBarPercent;
@@ -639,7 +640,8 @@
// Reset unused translations
primarySnapshot.setTranslationY(0);
} else {
- float scale = (float) totalThumbnailHeight / dp.availableHeightPx;
+ int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
+ float scale = (float) totalThumbnailHeight / (dp.availableHeightPx - taskbarHeight);
float topTaskHeight = dp.availableHeightPx * taskPercent;
float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
float scaledTopTaskHeight = topTaskHeight * scale;
diff --git a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
new file mode 100644
index 0000000..18f8339
--- /dev/null
+++ b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.util.Log
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import androidx.core.view.OneShotPreDrawListener
+import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.anim.AnimatorListeners
+import com.android.launcher3.anim.AnimatorPlaybackController
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.statemanager.StatefulActivity
+import java.util.function.Consumer
+
+private const val TAG = "CannedAnimCoordinator"
+
+/**
+ * Utility class to run a canned animation on Launcher.
+ *
+ * This class takes care to registering animations with stateManager and ensures that only one
+ * animation is playing at a time.
+ */
+class CannedAnimationCoordinator(private val activity: StatefulActivity<*>) {
+
+ private val launcherLayoutListener = OnGlobalLayoutListener { scheduleRecreateAnimOnPreDraw() }
+ private var recreatePending = false
+
+ private var animationProvider: Any? = null
+
+ private var animationDuration: Long = 0L
+ private var animationFactory: Consumer<PendingAnimation>? = null
+ private var animationController: AnimatorPlaybackController? = null
+
+ private var currentAnim: AnimatorPlaybackController? = null
+
+ /**
+ * Sets the current animation cancelling any previously set animation.
+ *
+ * Callers can control the animation using {@link #getPlaybackController}. The state is
+ * automatically cleared when the playback controller ends. The animation is automatically
+ * recreated when any layout change happens. Callers can also ask for recreation by calling
+ * {@link #recreateAnimation}
+ */
+ fun setAnimation(provider: Any, factory: Consumer<PendingAnimation>, duration: Long) {
+ if (provider != animationProvider) {
+ Log.e(TAG, "Trying to play two animations together, $provider and $animationProvider")
+ }
+
+ // Cancel any previously running animation
+ endCurrentAnimation(false)
+ animationController?.dispatchOnCancel()?.dispatchOnEnd()
+
+ animationProvider = provider
+ animationFactory = factory
+ animationDuration = duration
+
+ // Setup a new controller and link it with launcher state animation
+ val anim = AnimatorSet()
+ anim.play(
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ interpolator = LINEAR
+ this.duration = duration
+ addUpdateListener { anim -> currentAnim?.setPlayFraction(anim.animatedFraction) }
+ }
+ )
+ val controller = AnimatorPlaybackController.wrap(anim, duration)
+ anim.addListener(
+ AnimatorListeners.forEndCallback { success ->
+ if (animationController != controller) {
+ return@forEndCallback
+ }
+
+ endCurrentAnimation(success)
+ animationController = null
+ animationFactory = null
+ animationProvider = null
+
+ activity.rootView.viewTreeObserver.apply {
+ if (isAlive) {
+ removeOnGlobalLayoutListener(launcherLayoutListener)
+ }
+ }
+ }
+ )
+
+ // Recreate animation whenever layout happens in case transforms change during layout
+ activity.rootView.viewTreeObserver.apply {
+ if (isAlive) {
+ addOnGlobalLayoutListener(launcherLayoutListener)
+ }
+ }
+ // Link this to the state manager so that it auto-cancels when state changes
+ recreatePending = false
+ animationController =
+ controller.apply { activity.stateManager.setCurrentUserControlledAnimation(this) }
+ recreateAnimation(provider)
+ }
+
+ private fun endCurrentAnimation(success: Boolean) {
+ currentAnim?.apply {
+ // When cancelling an animation, apply final progress so that all transformations
+ // are restored
+ setPlayFraction(1f)
+ if (!success) dispatchOnCancel()
+ dispatchOnEnd()
+ }
+ currentAnim = null
+ }
+
+ /** Returns the current animation controller to control the animation */
+ fun getPlaybackController(provider: Any): AnimatorPlaybackController? {
+ return if (provider == animationProvider) animationController
+ else {
+ Log.d(TAG, "Wrong controller access from $provider, actual provider $animationProvider")
+ null
+ }
+ }
+
+ private fun scheduleRecreateAnimOnPreDraw() {
+ if (!recreatePending) {
+ recreatePending = true
+ OneShotPreDrawListener.add(activity.rootView) {
+ if (recreatePending) {
+ recreatePending = false
+ animationProvider?.apply { recreateAnimation(this) }
+ }
+ }
+ }
+ }
+
+ /** Notify the controller to recreate the animation. The animation progress is preserved */
+ fun recreateAnimation(provider: Any) {
+ if (provider != animationProvider) {
+ Log.e(TAG, "Ignore recreate request from $provider, actual provider $animationProvider")
+ return
+ }
+ endCurrentAnimation(false /* success */)
+
+ if (animationFactory == null || animationController == null) {
+ return
+ }
+ currentAnim =
+ PendingAnimation(animationDuration)
+ .apply { animationFactory?.accept(this) }
+ .createPlaybackController()
+ .apply { setPlayFraction(animationController!!.progressFraction) }
+ }
+}
diff --git a/src/com/android/launcher3/util/DimensionUtils.kt b/src/com/android/launcher3/util/DimensionUtils.kt
index 9188c2e..0eb0e08 100644
--- a/src/com/android/launcher3/util/DimensionUtils.kt
+++ b/src/com/android/launcher3/util/DimensionUtils.kt
@@ -29,9 +29,9 @@
*/
@JvmStatic
fun getTaskbarPhoneDimensions(
- deviceProfile: DeviceProfile,
- res: Resources,
- isPhoneMode: Boolean
+ deviceProfile: DeviceProfile,
+ res: Resources,
+ isPhoneMode: Boolean
): Point {
val p = Point()
// Taskbar for large screen
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 6647d0d..a7c94bb 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -54,8 +54,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -106,7 +106,8 @@
private final LauncherPrefs mPrefs;
- private DisplayController(Context context) {
+ @VisibleForTesting
+ protected DisplayController(Context context) {
mContext = context;
mDM = context.getSystemService(DisplayManager.class);
mPrefs = LauncherPrefs.get(context);
@@ -127,8 +128,7 @@
Context displayInfoContext = getDisplayInfoContext(display);
mInfo = new Info(displayInfoContext, wmProxy,
wmProxy.estimateInternalDisplayBounds(displayInfoContext));
- mInfo.mPerDisplayBounds.forEach((key, value) -> FileLog.i(TAG,
- "(CTOR) perDisplayBounds - " + key + ": " + Arrays.deepToString(value)));
+ FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
}
/**
@@ -286,9 +286,8 @@
if (!newInfo.supportedBounds.equals(oldInfo.supportedBounds)
|| !newInfo.mPerDisplayBounds.equals(oldInfo.mPerDisplayBounds)) {
change |= CHANGE_SUPPORTED_BOUNDS;
- newInfo.mPerDisplayBounds.forEach((key, value) -> FileLog.w(TAG,
- "(CHANGE_SUPPORTED_BOUNDS) perDisplayBounds - " + key + ": "
- + Arrays.deepToString(value)));
+ FileLog.w(TAG,
+ "(CHANGE_SUPPORTED_BOUNDS) perDisplayBounds: " + newInfo.mPerDisplayBounds);
}
if (DEBUG) {
Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
@@ -329,7 +328,7 @@
// WindowBounds
public final WindowBounds realBounds;
public final Set<WindowBounds> supportedBounds = new ArraySet<>();
- private final ArrayMap<CachedDisplayInfo, WindowBounds[]> mPerDisplayBounds =
+ private final ArrayMap<CachedDisplayInfo, List<WindowBounds>> mPerDisplayBounds =
new ArrayMap<>();
public Info(Context displayInfoContext) {
@@ -340,7 +339,7 @@
// Used for testing
public Info(Context displayInfoContext,
WindowManagerProxy wmProxy,
- Map<CachedDisplayInfo, WindowBounds[]> perDisplayBoundsCache) {
+ Map<CachedDisplayInfo, List<WindowBounds>> perDisplayBoundsCache) {
CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(displayInfoContext);
normalizedDisplayInfo = displayInfo.normalize();
rotation = displayInfo.rotation;
@@ -354,16 +353,14 @@
navigationMode = wmProxy.getNavigationMode(displayInfoContext);
mPerDisplayBounds.putAll(perDisplayBoundsCache);
- WindowBounds[] cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
+ List<WindowBounds> cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
realBounds = wmProxy.getRealBounds(displayInfoContext, displayInfo);
if (cachedValue == null) {
// Unexpected normalizedDisplayInfo is found, recreate the cache
FileLog.e(TAG, "Unexpected normalizedDisplayInfo found, invalidating cache: "
+ normalizedDisplayInfo);
- mPerDisplayBounds.forEach((key, value) -> FileLog.e(TAG,
- "(Invalid Cache) perDisplayBounds - " + key + ": " + Arrays.deepToString(
- value)));
+ FileLog.e(TAG, "(Invalid Cache) perDisplayBounds : " + mPerDisplayBounds);
mPerDisplayBounds.clear();
mPerDisplayBounds.putAll(wmProxy.estimateInternalDisplayBounds(displayInfoContext));
cachedValue = mPerDisplayBounds.get(normalizedDisplayInfo);
@@ -376,22 +373,19 @@
if (cachedValue != null) {
// Verify that the real bounds are a match
- WindowBounds expectedBounds = cachedValue[displayInfo.rotation];
+ WindowBounds expectedBounds = cachedValue.get(displayInfo.rotation);
if (!realBounds.equals(expectedBounds)) {
- WindowBounds[] clone = new WindowBounds[4];
- System.arraycopy(cachedValue, 0, clone, 0, 4);
- clone[displayInfo.rotation] = realBounds;
+ List<WindowBounds> clone = new ArrayList<>(cachedValue);
+ clone.set(displayInfo.rotation, realBounds);
mPerDisplayBounds.put(normalizedDisplayInfo, clone);
}
}
- mPerDisplayBounds.values().forEach(
- windowBounds -> Collections.addAll(supportedBounds, windowBounds));
+ mPerDisplayBounds.values().forEach(supportedBounds::addAll);
if (DEBUG) {
Log.d(TAG, "displayInfo: " + displayInfo);
Log.d(TAG, "realBounds: " + realBounds);
Log.d(TAG, "normalizedDisplayInfo: " + normalizedDisplayInfo);
- mPerDisplayBounds.forEach((key, value) -> Log.d(TAG,
- "perDisplayBounds - " + key + ": " + Arrays.deepToString(value)));
+ Log.d(TAG, "perDisplayBounds: " + mPerDisplayBounds);
}
}
@@ -448,7 +442,7 @@
pw.println(" navigationMode=" + info.navigationMode.name());
pw.println(" currentSize=" + info.currentSize);
info.mPerDisplayBounds.forEach((key, value) -> pw.println(
- " perDisplayBounds - " + key + ": " + Arrays.deepToString(value)));
+ " perDisplayBounds - " + key + ": " + value));
}
/**
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index 6978e0c..dec4b5c 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -21,6 +21,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@@ -58,6 +59,11 @@
new LooperExecutor(
createAndStartNewLooper("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND));
+
+ /** A background executor to preinflate views. */
+ public static final ExecutorService VIEW_PREINFLATION_EXECUTOR =
+ java.util.concurrent.Executors.newSingleThreadExecutor();
+
/**
* Utility method to get a started handler thread statically
*/
diff --git a/src/com/android/launcher3/util/IconSizeSteps.kt b/src/com/android/launcher3/util/IconSizeSteps.kt
new file mode 100644
index 0000000..2a5afe0
--- /dev/null
+++ b/src/com/android/launcher3/util/IconSizeSteps.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.res.Resources
+import androidx.core.content.res.getDimensionOrThrow
+import androidx.core.content.res.use
+import com.android.launcher3.R
+import kotlin.math.max
+
+class IconSizeSteps(res: Resources) {
+ private val steps: List<Int>
+
+ init {
+ steps =
+ res.obtainTypedArray(R.array.icon_size_steps).use {
+ (0 until it.length()).map { step -> it.getDimensionOrThrow(step).toInt() }.sorted()
+ }
+ }
+
+ fun minimumIconSize(): Int = steps[0]
+
+ fun getNextLowerIconSize(iconSizePx: Int): Int {
+ return steps[max(0, getIndexForIconSize(iconSizePx) - 1)]
+ }
+
+ fun getIconSmallerThan(cellWidth: Int): Int {
+ return steps.lastOrNull { it <= cellWidth } ?: steps[0]
+ }
+
+ private fun getIndexForIconSize(iconSizePx: Int): Int {
+ return max(0, steps.indexOfFirst { iconSizePx <= it })
+ }
+}
diff --git a/src/com/android/launcher3/util/InstantAppResolver.java b/src/com/android/launcher3/util/InstantAppResolver.java
index 6f706d2..bdb5e77 100644
--- a/src/com/android/launcher3/util/InstantAppResolver.java
+++ b/src/com/android/launcher3/util/InstantAppResolver.java
@@ -42,14 +42,7 @@
return false;
}
- public boolean isInstantApp(Context context, String packageName) {
- PackageManager packageManager = context.getPackageManager();
- try {
- return isInstantApp(packageManager.getPackageInfo(packageName, 0).applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e("InstantAppResolver", "Failed to determine whether package is instant app "
- + packageName, e);
- }
+ public boolean isInstantApp(String packageName, int userId) {
return false;
}
}
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index 1c78795..2498242 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -250,6 +250,11 @@
return b.toString();
}
+ @Override
+ public String toString() {
+ return "IntArray [" + toConcatString() + "]";
+ }
+
public static IntArray fromConcatString(String concatString) {
StringTokenizer tokenizer = new StringTokenizer(concatString, ",");
int[] array = new int[tokenizer.countTokens()];
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index 1231604..0a87594 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -1,3 +1,18 @@
+/*
+ * 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
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 6a4e528..1cb9994 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -48,8 +48,8 @@
}
public T get(Context context) {
- if (context instanceof SandboxContext) {
- return ((SandboxContext) context).getObject(this, mProvider);
+ if (context instanceof SandboxContext sc) {
+ return sc.getObject(this);
}
if (mValue == null) {
@@ -131,23 +131,22 @@
* Find a cached object from mObjectMap if we have already created one. If not, generate
* an object using the provider.
*/
- private <T> T getObject(MainThreadInitializedObject<T> object, ObjectProvider<T> provider) {
+ protected <T> T getObject(MainThreadInitializedObject<T> object) {
synchronized (mDestroyLock) {
if (mDestroyed) {
Log.e(TAG, "Static object access with a destroyed context");
}
-
T t = (T) mObjectMap.get(object);
if (t != null) {
return t;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
- t = createObject(provider);
+ t = createObject(object);
// Check if we've explicitly allowed the object or if it's a SafeCloseable,
// it will get destroyed in onDestroy()
if (!mAllowedObjects.contains(object) && !(t instanceof SafeCloseable)) {
- throw new IllegalStateException(
- "Leaking unknown objects " + object + " " + provider + " " + t);
+ throw new IllegalStateException("Leaking unknown objects "
+ + object + " " + object.mProvider + " " + t);
}
mObjectMap.put(object, t);
mOrderedObjects.add(t);
@@ -156,15 +155,15 @@
}
try {
- return MAIN_EXECUTOR.submit(() -> getObject(object, provider)).get();
+ return MAIN_EXECUTOR.submit(() -> getObject(object)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@UiThread
- protected <T> T createObject(ObjectProvider<T> provider) {
- return provider.get(this);
+ protected <T> T createObject(MainThreadInitializedObject<T> object) {
+ return object.mProvider.get(this);
}
}
}
diff --git a/src/com/android/launcher3/util/MultiScalePropertyFactory.java b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
index a7e6cc8..cf8d6cc 100644
--- a/src/com/android/launcher3/util/MultiScalePropertyFactory.java
+++ b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
@@ -40,8 +40,7 @@
private static final boolean DEBUG = false;
private static final String TAG = "MultiScaleProperty";
private final String mName;
- private final ArrayMap<Integer, MultiScaleProperty> mProperties =
- new ArrayMap<Integer, MultiScaleProperty>();
+ private final ArrayMap<Integer, MultiScaleProperty> mProperties = new ArrayMap<>();
// This is an optimization for cases when set is called repeatedly with the same setterIndex.
private float mMinOfOthers = 0;
@@ -55,7 +54,7 @@
}
/** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
- public MultiScaleProperty get(Integer index) {
+ public FloatProperty<T> get(Integer index) {
return mProperties.computeIfAbsent(index,
(k) -> new MultiScaleProperty(index, mName + "_" + index));
}
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index ac016a8..a66a9d2 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -32,8 +32,15 @@
// Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
private boolean mUpdateVisibility;
+ private final int mHiddenVisibility;
+
public MultiValueAlpha(View view, int size) {
+ this(view, size, View.INVISIBLE);
+ }
+
+ public MultiValueAlpha(View view, int size, int hiddenVisibility) {
super(view, VIEW_ALPHA, size, ALPHA_AGGREGATOR, 1f);
+ this.mHiddenVisibility = hiddenVisibility;
}
/** Sets whether we should update between INVISIBLE and VISIBLE based on alpha. */
@@ -45,7 +52,7 @@
protected void apply(float value) {
super.apply(value);
if (mUpdateVisibility) {
- AlphaUpdateListener.updateVisibility(mTarget);
+ AlphaUpdateListener.updateVisibility(mTarget, mHiddenVisibility);
}
}
}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 348c8d8..f8f4b5f 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -38,8 +38,6 @@
public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
- public static final String SEARCH_KEYBOARD_EDU_SEEN = "launcher.search_edu_seen";
- public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
public static final String TASKBAR_EDU_TOOLTIP_STEP = "launcher.taskbar_edu_tooltip_step";
// When adding a new key, add it here as well, to be able to reset it from Developer Options.
@@ -47,7 +45,6 @@
"All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
"Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
HOTSEAT_LONGPRESS_TIP_SEEN },
- "Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT},
"Taskbar Education", new String[] { TASKBAR_EDU_TOOLTIP_STEP },
"All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
);
@@ -58,7 +55,6 @@
@StringDef(value = {
HOME_BOUNCE_SEEN,
HOTSEAT_LONGPRESS_TIP_SEEN,
- SEARCH_KEYBOARD_EDU_SEEN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventBoolKey {}
@@ -69,7 +65,6 @@
@StringDef(value = {
HOME_BOUNCE_COUNT,
HOTSEAT_DISCOVERY_TIP_COUNT,
- SEARCH_SNACKBAR_COUNT,
ALL_APPS_VISITED_COUNT,
TASKBAR_EDU_TOOLTIP_STEP,
})
@@ -82,7 +77,6 @@
Map<String, Integer> maxCounts = new ArrayMap<>(5);
maxCounts.put(HOME_BOUNCE_COUNT, 3);
maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
- maxCounts.put(SEARCH_SNACKBAR_COUNT, 3);
maxCounts.put(ALL_APPS_VISITED_COUNT, 20);
maxCounts.put(TASKBAR_EDU_TOOLTIP_STEP, 2);
MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 1d6bc25..91203a7 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -164,13 +164,6 @@
}
}
- public static Intent getStyleWallpapersIntent(Context context) {
- return new Intent(Intent.ACTION_SET_WALLPAPER).setComponent(
- new ComponentName(context.getString(R.string.wallpaper_picker_package),
- context.getString(R.string.custom_activity_picker)
- ));
- }
-
/**
* Starts the details activity for {@code info}
*/
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 0c5b722..06cb00e 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -154,7 +154,7 @@
*/
public void unregister(Uri uri, OnChangeListener listener) {
List<OnChangeListener> listenersToRemoveFrom = mListenerMap.get(uri);
- if (!listenersToRemoveFrom.contains(listener)) {
+ if (listenersToRemoveFrom == null) {
return;
}
diff --git a/src/com/android/launcher3/util/StartActivityParams.java b/src/com/android/launcher3/util/StartActivityParams.java
index 6e60b2a..b48562f 100644
--- a/src/com/android/launcher3/util/StartActivityParams.java
+++ b/src/com/android/launcher3/util/StartActivityParams.java
@@ -20,7 +20,10 @@
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+import static com.android.launcher3.Utilities.allowBGLaunch;
+
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
@@ -95,9 +98,10 @@
/** Perform the operation on the pendingIntent. */
public void deliverResult(Context context, int resultCode, Intent data) {
+ ActivityOptions options = allowBGLaunch(ActivityOptions.makeBasic());
try {
if (mPICallback != null) {
- mPICallback.send(context, resultCode, data);
+ mPICallback.send(context, resultCode, data, null, null, null, options.toBundle());
}
} catch (CanceledException e) {
Log.e(TAG, "Unable to send back result", e);
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index a5c663f..60951ba 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -21,8 +21,6 @@
import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
@@ -48,16 +46,9 @@
public static final String KEY_THEMED_ICONS = "themed_icons";
+ /** Gets the WallpaperColorHints and then uses those to get the correct activity theme res. */
public static int getActivityThemeRes(Context context) {
- final int colorHints;
- if (Utilities.ATLEAST_P) {
- WallpaperColors colors = context.getSystemService(WallpaperManager.class)
- .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
- colorHints = colors == null ? 0 : colors.getColorHints();
- } else {
- colorHints = 0;
- }
- return getActivityThemeRes(context, colorHints);
+ return getActivityThemeRes(context, WallpaperColorHints.get(context).getHints());
}
public static int getActivityThemeRes(Context context, int wallpaperColorHints) {
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
index c23df77..138cc4a 100644
--- a/src/com/android/launcher3/util/TraceHelper.java
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -15,12 +15,17 @@
*/
package com.android.launcher3.util;
+import android.annotation.SuppressLint;
import android.os.Trace;
import androidx.annotation.MainThread;
+import com.android.launcher3.Utilities;
+
import java.util.function.Supplier;
+import kotlin.random.Random;
+
/**
* A wrapper around {@link Trace} to allow better testing.
*
@@ -36,54 +41,63 @@
// Temporarily ignore blocking binder calls for this trace.
public static final int FLAG_IGNORE_BINDERS = 1 << 1;
- public static final int FLAG_CHECK_FOR_RACE_CONDITIONS = 1 << 2;
-
- public static final int FLAG_UI_EVENT =
- FLAG_ALLOW_BINDER_TRACKING | FLAG_CHECK_FOR_RACE_CONDITIONS;
-
/**
* Static instance of Trace helper, overridden in tests.
*/
public static TraceHelper INSTANCE = new TraceHelper();
/**
- * @return a token to pass into {@link #endSection(Object)}.
+ * @see Trace#beginSection(String)
*/
- public Object beginSection(String sectionName) {
- return beginSection(sectionName, 0);
- }
-
- public Object beginSection(String sectionName, int flags) {
+ public void beginSection(String sectionName) {
Trace.beginSection(sectionName);
- return null;
}
/**
- * @param token the token returned from {@link #beginSection(String, int)}
+ * @see Trace#endSection()
*/
- public void endSection(Object token) {
+ public void endSection() {
Trace.endSection();
}
/**
- * Similar to {@link #beginSection} but doesn't add a trace section.
+ * @see Trace#beginAsyncSection(String, int)
+ * @return a SafeCloseable that can be used to end the session
*/
- public Object beginFlagsOverride(int flags) {
- return null;
+ @SuppressWarnings("NewApi")
+ @SuppressLint("NewApi")
+ public SafeCloseable beginAsyncSection(String sectionName) {
+ if (!Utilities.ATLEAST_Q) {
+ return () -> { };
+ }
+ int cookie = Random.Default.nextInt();
+ Trace.beginAsyncSection(sectionName, cookie);
+ return () -> Trace.endAsyncSection(sectionName, cookie);
}
- public void endFlagsOverride(Object token) { }
+ /**
+ * Returns a SafeCloseable to temporarily ignore blocking binder calls.
+ */
+ @SuppressWarnings("NewApi")
+ @SuppressLint("NewApi")
+ public SafeCloseable allowIpcs(String rpcName) {
+ if (!Utilities.ATLEAST_Q) {
+ return () -> { };
+ }
+ int cookie = Random.Default.nextInt();
+ Trace.beginAsyncSection(rpcName, cookie);
+ return () -> Trace.endAsyncSection(rpcName, cookie);
+ }
/**
* Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
+ *
+ * Note, new features should be designed to not rely on mainThread RPCs.
*/
@MainThread
public static <T> T allowIpcs(String rpcName, Supplier<T> supplier) {
- Object traceToken = INSTANCE.beginSection(rpcName, FLAG_IGNORE_BINDERS);
- try {
+ try (SafeCloseable c = INSTANCE.allowIpcs(rpcName)) {
return supplier.get();
- } finally {
- INSTANCE.endSection(traceToken);
}
}
}
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index ceba0db..91945ca 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -17,6 +17,7 @@
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;
@@ -68,6 +69,9 @@
@Nullable
private final VibrationEffect mBumpEffect;
+ @Nullable
+ private final VibrationEffect mAssistEffect;
+
private long mLastDragTime;
private final int mThresholdUntilNextDragCallMillis;
@@ -125,12 +129,25 @@
mBumpEffect = null;
mThresholdUntilNextDragCallMillis = 0;
}
+
+ if (Utilities.ATLEAST_R && mVibrator.areAllPrimitivesSupported(
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+ VibrationEffect.Composition.PRIMITIVE_TICK)) {
+ // quiet ramp, short pause, then sharp tick
+ mAssistEffect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
+ .compose();
+ } else {
+ // fallback for devices without composition support
+ mAssistEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK);
+ }
}
/**
- * 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().
+ * 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) {
@@ -145,7 +162,7 @@
}
/**
- * This is used when user reaches the commit threshold when swiping to/from from all apps.
+ * This is used when user reaches the commit threshold when swiping to/from from all apps.
*/
public void vibrateForDragCommit() {
if (mCommitEffect != null) {
@@ -156,9 +173,9 @@
}
/**
- * 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.
+ * 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) {
@@ -167,6 +184,15 @@
}
/**
+ * The assist haptic is used to be called when an assistant is invoked
+ */
+ public void vibrateForAssist() {
+ if (mAssistEffect != null) {
+ vibrate(mAssistEffect);
+ }
+ }
+
+ /**
* 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)}.
@@ -176,6 +202,7 @@
// 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/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt
new file mode 100644
index 0000000..1361c1e
--- /dev/null
+++ b/src/com/android/launcher3/util/WallpaperColorHints.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.app.WallpaperColors
+import android.app.WallpaperManager
+import android.app.WallpaperManager.FLAG_SYSTEM
+import android.app.WallpaperManager.OnColorsChangedListener
+import android.content.Context
+import androidx.annotation.MainThread
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.Utilities
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+
+/**
+ * This class caches the system's wallpaper color hints for use by other classes as a performance
+ * enhancer. It also centralizes all the WallpaperManager color hint code in one location.
+ */
+class WallpaperColorHints(private val context: Context) : SafeCloseable {
+ var hints: Int = 0
+ private set
+ private val wallpaperManager
+ get() = context.getSystemService(WallpaperManager::class.java)!!
+ private val onColorHintsChangedListeners = mutableListOf<OnColorHintListener>()
+ private val onClose: SafeCloseable
+
+ init {
+ if (Utilities.ATLEAST_S) {
+ hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
+ val onColorsChangedListener = OnColorsChangedListener { colors, which ->
+ onColorsChanged(colors, which)
+ }
+ UI_HELPER_EXECUTOR.execute {
+ wallpaperManager.addOnColorsChangedListener(
+ onColorsChangedListener,
+ MAIN_EXECUTOR.handler
+ )
+ }
+ onClose = SafeCloseable {
+ UI_HELPER_EXECUTOR.execute {
+ wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
+ }
+ }
+ } else {
+ onClose = SafeCloseable {}
+ }
+ }
+
+ @MainThread
+ private fun onColorsChanged(colors: WallpaperColors?, which: Int) {
+ if ((which and FLAG_SYSTEM) != 0 && Utilities.ATLEAST_S) {
+ val newHints = colors?.colorHints ?: 0
+ if (newHints != hints) {
+ hints = newHints
+ onColorHintsChangedListeners.forEach { it.onColorHintsChanged(newHints) }
+ }
+ }
+ }
+
+ override fun close() = onClose.close()
+
+ fun registerOnColorHintsChangedListener(listener: OnColorHintListener) {
+ onColorHintsChangedListeners.add(listener)
+ }
+
+ fun unregisterOnColorsChangedListener(listener: OnColorHintListener) {
+ onColorHintsChangedListeners.remove(listener)
+ }
+
+ companion object {
+ @VisibleForTesting
+ @JvmField
+ val INSTANCE = MainThreadInitializedObject { WallpaperColorHints(it) }
+ @JvmStatic fun get(context: Context): WallpaperColorHints = INSTANCE.get(context)
+ }
+}
+
+interface OnColorHintListener {
+ fun onColorHintsChanged(colorHints: Int)
+}
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 4ac6bc4..b97b889 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -15,9 +15,9 @@
import androidx.annotation.AnyThread;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
/**
* Utility class to handle wallpaper scrolling along with workspace.
@@ -237,7 +237,7 @@
public OffsetHandler(Context context) {
super(UI_HELPER_EXECUTOR.getLooper());
- mInterpolator = Interpolators.DEACCEL_1_5;
+ mInterpolator = Interpolators.DECELERATE_1_5;
mWM = WallpaperManager.getInstance(context);
}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 4093bc9..278a37e 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -57,6 +57,9 @@
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.WindowBounds;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Utility class for mocking some window manager behaviours
*/
@@ -90,11 +93,11 @@
* Returns a map of normalized info of internal displays to estimated window bounds
* for that display
*/
- public ArrayMap<CachedDisplayInfo, WindowBounds[]> estimateInternalDisplayBounds(
+ public ArrayMap<CachedDisplayInfo, List<WindowBounds>> estimateInternalDisplayBounds(
Context displayInfoContext) {
CachedDisplayInfo info = getDisplayInfo(displayInfoContext).normalize();
- WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info);
- ArrayMap<CachedDisplayInfo, WindowBounds[]> result = new ArrayMap<>();
+ List<WindowBounds> bounds = estimateWindowBounds(displayInfoContext, info);
+ ArrayMap<CachedDisplayInfo, List<WindowBounds>> result = new ArrayMap<>();
result.put(info, bounds);
return result;
}
@@ -200,7 +203,8 @@
/**
* Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations
*/
- protected WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo displayInfo) {
+ protected List<WindowBounds> estimateWindowBounds(Context context,
+ CachedDisplayInfo displayInfo) {
int densityDpi = context.getResources().getConfiguration().densityDpi;
int rotation = displayInfo.rotation;
Rect safeCutout = displayInfo.cutout;
@@ -243,7 +247,7 @@
? 0
: getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
- WindowBounds[] result = new WindowBounds[4];
+ List<WindowBounds> result = new ArrayList<>(4);
Point tempSize = new Point();
for (int i = 0; i < 4; i++) {
int rotationChange = deltaRotation(rotation, i);
@@ -274,7 +278,7 @@
} else {
insets.right = Math.max(insets.right, navbarWidth);
}
- result[i] = new WindowBounds(bounds, insets, i);
+ result.add(new WindowBounds(bounds, insets, i));
}
return result;
}
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index ec7ec0b..30e0971 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -17,24 +17,22 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.allapps.AllAppsTransitionController.REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.Property;
+import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -47,10 +45,13 @@
import androidx.annotation.Px;
import androidx.annotation.RequiresApi;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
@@ -66,8 +67,8 @@
public abstract class AbstractSlideInView<T extends Context & ActivityContext>
extends AbstractFloatingView implements SingleAxisSwipeDetector.Listener {
- protected static final Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
- new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
+ protected static final FloatProperty<AbstractSlideInView<?>> TRANSLATION_SHIFT =
+ new FloatProperty<>("translationShift") {
@Override
public Float get(AbstractSlideInView view) {
@@ -75,25 +76,54 @@
}
@Override
- public void set(AbstractSlideInView view, Float value) {
+ public void setValue(AbstractSlideInView view, float value) {
view.setTranslationShift(value);
}
};
protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
private static final float VIEW_NO_SCALE = 1f;
+ private static final int DEFAULT_DURATION = 300;
protected final T mActivityContext;
protected final SingleAxisSwipeDetector mSwipeDetector;
- protected final ObjectAnimator mOpenCloseAnimator;
+ protected @NonNull AnimatorPlaybackController mOpenCloseAnimation;
protected ViewGroup mContent;
protected final View mColorScrim;
- protected Interpolator mScrollInterpolator;
+
+ /**
+ * Interpolator for {@link #mOpenCloseAnimation} when we are closing due to dragging downwards.
+ */
+ private Interpolator mScrollInterpolator;
+ private long mScrollDuration;
+ /**
+ * End progress for {@link #mOpenCloseAnimation} when we are closing due to dragging downloads.
+ * <p>
+ * There are two cases that determine this value:
+ * <ol>
+ * <li>
+ * If the drag interrupts the opening transition (i.e. {@link #mToTranslationShift}
+ * is {@link #TRANSLATION_SHIFT_OPENED}), we need to animate back to {@code 0} to
+ * reverse the animation that was paused at {@link #onDragStart(boolean, float)}.
+ * </li>
+ * <li>
+ * If the drag started after the view is fully opened (i.e.
+ * {@link #mToTranslationShift} is {@link #TRANSLATION_SHIFT_CLOSED}), the animation
+ * that was set up at {@link #onDragStart(boolean, float)} for closing the view
+ * should go forward to {@code 1}.
+ * </li>
+ * </ol>
+ */
+ private float mScrollEndProgress;
// range [0, 1], 0=> completely open, 1=> completely closed
protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
+ protected float mFromTranslationShift;
+ protected float mToTranslationShift;
+ /** {@link #mOpenCloseAnimation} progress at {@link #onDragStart(boolean, float)}. */
+ private float mDragStartProgress;
protected boolean mNoIntercept;
protected @Nullable OnCloseListener mOnCloseBeginListener;
@@ -102,8 +132,8 @@
protected final AnimatedFloat mSlideInViewScale =
new AnimatedFloat(this::onScaleProgressChanged, VIEW_NO_SCALE);
protected boolean mIsBackProgressing;
- @Nullable private Drawable mContentBackground;
- @Nullable private View mContentBackgroundParentView;
+ private @Nullable Drawable mContentBackground;
+ private @Nullable View mContentBackgroundParentView;
protected final ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
@Override
@@ -122,21 +152,78 @@
mActivityContext = ActivityContext.lookupContext(context);
mScrollInterpolator = Interpolators.SCROLL_CUBIC;
+ mScrollDuration = DEFAULT_DURATION;
mSwipeDetector = new SingleAxisSwipeDetector(context, this,
SingleAxisSwipeDetector.VERTICAL);
- mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
- mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mSwipeDetector.finishedScrolling();
- announceAccessibilityChanges();
- }
- });
+ mOpenCloseAnimation = new PendingAnimation(0).createPlaybackController();
+
int scrimColor = getScrimColor(context);
mColorScrim = scrimColor != -1 ? createColorScrim(context, scrimColor) : null;
}
+ /**
+ * Sets up a {@link #mOpenCloseAnimation} for opening with default parameters.
+ *
+ * @see #setUpOpenCloseAnimation(float, float, long)
+ */
+ protected final AnimatorPlaybackController setUpDefaultOpenAnimation() {
+ AnimatorPlaybackController animation = setUpOpenCloseAnimation(
+ TRANSLATION_SHIFT_CLOSED, TRANSLATION_SHIFT_OPENED, DEFAULT_DURATION);
+ animation.getAnimationPlayer().setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ return animation;
+ }
+
+ /**
+ * Sets up a {@link #mOpenCloseAnimation} for opening with a given duration.
+ *
+ * @see #setUpOpenCloseAnimation(float, float, long)
+ */
+ protected final AnimatorPlaybackController setUpOpenAnimation(long duration) {
+ return setUpOpenCloseAnimation(
+ TRANSLATION_SHIFT_CLOSED, TRANSLATION_SHIFT_OPENED, duration);
+ }
+
+ private AnimatorPlaybackController setUpCloseAnimation(long duration) {
+ return setUpOpenCloseAnimation(
+ TRANSLATION_SHIFT_OPENED, TRANSLATION_SHIFT_CLOSED, duration);
+ }
+
+ /**
+ * Initializes a new {@link #mOpenCloseAnimation}.
+ *
+ * @param fromTranslationShift translation shift to animate from.
+ * @param toTranslationShift translation shift to animate to.
+ * @param duration animation duration.
+ * @return {@link #mOpenCloseAnimation}
+ */
+ private AnimatorPlaybackController setUpOpenCloseAnimation(
+ float fromTranslationShift, float toTranslationShift, long duration) {
+ mFromTranslationShift = fromTranslationShift;
+ mToTranslationShift = toTranslationShift;
+
+ PendingAnimation animation = new PendingAnimation(duration);
+ animation.addEndListener(b -> {
+ mSwipeDetector.finishedScrolling();
+ announceAccessibilityChanges();
+ });
+
+ animation.addFloat(
+ this, TRANSLATION_SHIFT, fromTranslationShift, toTranslationShift, LINEAR);
+ onOpenCloseAnimationPending(animation);
+
+ mOpenCloseAnimation = animation.createPlaybackController();
+ return mOpenCloseAnimation;
+ }
+
+ /**
+ * Invoked when a {@link #mOpenCloseAnimation} is being set up.
+ * <p>
+ * Subclasses can override this method to modify the animation before it's used to create a
+ * {@link AnimatorPlaybackController}.
+ */
+ protected void onOpenCloseAnimationPending(PendingAnimation animation) {}
+
protected void attachToContainer() {
if (mColorScrim != null) {
getPopupContainer().addView(mColorScrim);
@@ -279,19 +366,28 @@
}
private boolean isOpeningAnimationRunning() {
- return mIsOpen && mOpenCloseAnimator.isRunning();
+ return mIsOpen && mOpenCloseAnimation.getAnimationPlayer().isRunning();
}
/* SingleAxisSwipeDetector.Listener */
@Override
- public void onDragStart(boolean start, float startDisplacement) { }
+ public void onDragStart(boolean start, float startDisplacement) {
+ if (mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
+ mOpenCloseAnimation.pause();
+ mDragStartProgress = mOpenCloseAnimation.getProgressFraction();
+ } else {
+ setUpCloseAnimation(DEFAULT_DURATION);
+ mDragStartProgress = 0;
+ }
+ }
@Override
public boolean onDrag(float displacement) {
- float range = getShiftRange();
- displacement = Utilities.boundToRange(displacement, 0, range);
- setTranslationShift(displacement / range);
+ float progress = mDragStartProgress
+ + Math.signum(mToTranslationShift - mFromTranslationShift)
+ * (displacement / getShiftRange());
+ mOpenCloseAnimation.setPlayFraction(Utilities.boundToRange(progress, 0, 1));
return true;
}
@@ -302,16 +398,18 @@
if ((mSwipeDetector.isFling(velocity) && velocity > 0)
|| mTranslationShift > successfulShiftThreshold) {
mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
- mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration(
- velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
+ mScrollDuration = BaseSwipeDetector.calculateDuration(
+ velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift);
+ mScrollEndProgress = mToTranslationShift == TRANSLATION_SHIFT_OPENED ? 0 : 1;
close(true);
} else {
- mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
- TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setDuration(
- BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
- .setInterpolator(Interpolators.DEACCEL);
- mOpenCloseAnimator.start();
+ ValueAnimator animator = mOpenCloseAnimation.getAnimationPlayer();
+ animator.setInterpolator(Interpolators.DECELERATE);
+ animator.setFloatValues(
+ mOpenCloseAnimation.getProgressFraction(),
+ mToTranslationShift == TRANSLATION_SHIFT_OPENED ? 1 : 0);
+ animator.setDuration(BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
+ .start();
}
}
@@ -332,32 +430,31 @@
Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
if (!animate) {
- mOpenCloseAnimator.cancel();
+ mOpenCloseAnimation.pause();
setTranslationShift(TRANSLATION_SHIFT_CLOSED);
onCloseComplete();
return;
}
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
- mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator.removeListener(this);
- onCloseComplete();
- }
- });
+
+ final ValueAnimator animator;
if (mSwipeDetector.isIdleState()) {
- mOpenCloseAnimator
- .setDuration(defaultDuration)
- .setInterpolator(getIdleInterpolator());
+ setUpCloseAnimation(defaultDuration);
+ animator = mOpenCloseAnimation.getAnimationPlayer();
+ animator.setInterpolator(getIdleInterpolator());
} else {
- mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
+ animator = mOpenCloseAnimation.getAnimationPlayer();
+ animator.setInterpolator(mScrollInterpolator);
+ animator.setDuration(mScrollDuration);
+ mOpenCloseAnimation.getAnimationPlayer().setFloatValues(
+ mOpenCloseAnimation.getProgressFraction(), mScrollEndProgress);
}
- mOpenCloseAnimator.start();
+
+ animator.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
+ animator.start();
}
protected Interpolator getIdleInterpolator() {
- return Interpolators.ACCEL;
+ return Interpolators.ACCELERATE;
}
protected void onCloseComplete() {
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 4b319e5..84ea871 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -18,6 +18,7 @@
import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
+import static com.android.launcher3.Utilities.allowBGLaunch;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_PENDING_INTENT;
@@ -38,7 +39,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
-import android.os.StrictMode;
import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -239,6 +239,11 @@
};
}
+ /** Long-click callback used for All Apps items. */
+ default View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return v -> false;
+ }
+
@Nullable
default PopupDataProvider getPopupDataProvider() {
return null;
@@ -338,6 +343,12 @@
return null;
}
+ boolean isShortcut = (item instanceof WorkspaceItemInfo)
+ && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && !((WorkspaceItemInfo) item).isPromise();
+ if (isShortcut && GO_DISABLE_WIDGETS) {
+ return null;
+ }
ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item)
: makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON
? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */);
@@ -349,13 +360,11 @@
intent.setSourceBounds(Utilities.getViewBounds(v));
}
try {
- boolean isShortcut = (item instanceof WorkspaceItemInfo)
- && (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
- || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
- && !((WorkspaceItemInfo) item).isPromise();
if (isShortcut) {
- // Shortcuts need some special checks due to legacy reasons.
- startShortcutIntentSafely(intent, optsBundle, item);
+ String id = ((WorkspaceItemInfo) item).getDeepShortcutId();
+ String packageName = intent.getPackage();
+ ((Context) this).getSystemService(LauncherApps.class).startShortcut(
+ packageName, id, intent.getSourceBounds(), optsBundle, user);
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
context.startActivity(intent, optsBundle);
@@ -410,8 +419,7 @@
}
}
ActivityOptions options =
- ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
-
+ allowBGLaunch(ActivityOptions.makeClipRevealAnimation(v, left, top, width, height));
options.setLaunchDisplayId(
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
: Display.DEFAULT_DISPLAY);
@@ -423,62 +431,13 @@
* Creates a default activity option and we do not want association with any launcher element.
*/
default ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
- ActivityOptions options = ActivityOptions.makeBasic();
+ ActivityOptions options = allowBGLaunch(ActivityOptions.makeBasic());
if (Utilities.ATLEAST_T) {
options.setSplashScreenStyle(splashScreenStyle);
}
return new ActivityOptionsWrapper(options, new RunnableList());
}
- /**
- * Safely launches an intent for a shortcut.
- *
- * @param intent Intent to start.
- * @param optsBundle Optional launch arguments.
- * @param info Shortcut information.
- */
- default void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
- try {
- StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
- try {
- // Temporarily disable deathPenalty on all default checks. For eg, shortcuts
- // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure
- // is enabled by default on NYC.
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
- .penaltyLog().build());
-
- if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
- String packageName = intent.getPackage();
- startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
- } else {
- // Could be launching some bookkeeping activity
- ((Context) this).startActivity(intent, optsBundle);
- }
- } finally {
- StrictMode.setVmPolicy(oldPolicy);
- }
- } catch (SecurityException e) {
- throw e;
- }
- }
-
- /**
- * A wrapper around the platform method with Launcher specific checks.
- */
- default void startShortcut(String packageName, String id, Rect sourceBounds,
- Bundle startActivityOptions, UserHandle user) {
- if (GO_DISABLE_WIDGETS) {
- return;
- }
- try {
- ((Context) this).getSystemService(LauncherApps.class).startShortcut(packageName, id,
- sourceBounds, startActivityOptions, user);
- } catch (SecurityException | IllegalStateException e) {
- Log.e(TAG, "Failed to start shortcut", e);
- }
- }
-
default CellPosMapper getCellPosMapper() {
return CellPosMapper.DEFAULT;
}
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index 73c5ad4..b44dbeb 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -16,14 +16,21 @@
package com.android.launcher3.views;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.TypedArray;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Handler;
+import android.util.IntProperty;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -33,18 +40,17 @@
import androidx.annotation.Nullable;
import androidx.annotation.Px;
-import androidx.core.content.ContextCompat;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.TriangleShape;
/**
- * A base class for arrow tip view in launcher
+ * A base class for arrow tip view in launcher.
*/
public class ArrowTipView extends AbstractFloatingView {
@@ -54,33 +60,66 @@
private static final long SHOW_DURATION_MS = 300;
private static final long HIDE_DURATION_MS = 100;
- protected final BaseDraggingActivity mActivity;
+ public static final IntProperty<ArrowTipView> TEXT_ALPHA =
+ new IntProperty<>("textAlpha") {
+ @Override
+ public void setValue(ArrowTipView view, int v) {
+ view.setTextAlpha(v);
+ }
+
+ @Override
+ public Integer get(ArrowTipView view) {
+ return view.getTextAlpha();
+ }
+ };
+
+ private final ActivityContext mActivityContext;
private final Handler mHandler = new Handler();
- private final int mArrowWidth;
- private final int mArrowMinOffset;
private boolean mIsPointingUp;
private Runnable mOnClosed;
private View mArrowView;
+ private final int mArrowWidth;
+ private final int mArrowMinOffset;
+ private final int mArrowViewPaintColor;
+
+ private AnimatorSet mOpenAnimator = new AnimatorSet();
+ private AnimatorSet mCloseAnimator = new AnimatorSet();
+
+ private int mTextAlpha;
public ArrowTipView(Context context) {
this(context, false);
}
public ArrowTipView(Context context, boolean isPointingUp) {
+ this(context, isPointingUp, R.layout.arrow_toast);
+ }
+
+ public ArrowTipView(Context context, boolean isPointingUp, int layoutId) {
super(context, null, 0);
- mActivity = BaseDraggingActivity.fromContext(context);
+ mActivityContext = ActivityContext.lookupContext(context);
mIsPointingUp = isPointingUp;
- mArrowWidth = context.getResources().getDimensionPixelSize(R.dimen.arrow_toast_arrow_width);
+ mArrowWidth = context.getResources().getDimensionPixelSize(
+ R.dimen.arrow_toast_arrow_width);
mArrowMinOffset = context.getResources().getDimensionPixelSize(
R.dimen.dynamic_grid_cell_border_spacing);
- init(context);
+ TypedArray ta = context.obtainStyledAttributes(R.styleable.ArrowTipView);
+ // Set style to default to avoid inflation issues with missing attributes.
+ if (!ta.hasValue(R.styleable.ArrowTipView_arrowTipBackground)
+ || !ta.hasValue(R.styleable.ArrowTipView_arrowTipTextColor)) {
+ context = new ContextThemeWrapper(context, R.style.ArrowTipStyle);
+ }
+ mArrowViewPaintColor = ta.getColor(R.styleable.ArrowTipView_arrowTipBackground,
+ context.getColor(R.color.arrow_tip_view_bg));
+ ta.recycle();
+ init(context, layoutId);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
close(true);
- if (mActivity.getDragLayer().isEventOverView(this, ev)) {
+ if (mActivityContext.getDragLayer().isEventOverView(this, ev)) {
return true;
}
}
@@ -89,18 +128,17 @@
@Override
protected void handleClose(boolean animate) {
+ if (mOpenAnimator.isStarted()) {
+ mOpenAnimator.cancel();
+ }
if (mIsOpen) {
if (animate) {
- animate().alpha(0f)
- .withLayer()
- .setStartDelay(0)
- .setDuration(HIDE_DURATION_MS)
- .setInterpolator(Interpolators.ACCEL)
- .withEndAction(() -> mActivity.getDragLayer().removeView(this))
- .start();
+ mCloseAnimator.addListener(AnimatorListeners.forSuccessCallback(
+ () -> mActivityContext.getDragLayer().removeView(this)));
+ mCloseAnimator.start();
} else {
- animate().cancel();
- mActivity.getDragLayer().removeView(this);
+ mCloseAnimator.cancel();
+ mActivityContext.getDragLayer().removeView(this);
}
if (mOnClosed != null) mOnClosed.run();
mIsOpen = false;
@@ -112,12 +150,31 @@
return (type & TYPE_ON_BOARD_POPUP) != 0;
}
- private void init(Context context) {
- inflate(context, R.layout.arrow_toast, this);
+ private void init(Context context, int layoutId) {
+ inflate(context, layoutId, this);
setOrientation(LinearLayout.VERTICAL);
mArrowView = findViewById(R.id.arrow);
updateArrowTipInView();
+ setAlpha(0);
+
+ // Create default open animator.
+ mOpenAnimator.play(ObjectAnimator.ofFloat(this, ALPHA, 1f));
+ mOpenAnimator.setStartDelay(SHOW_DELAY_MS);
+ mOpenAnimator.setDuration(SHOW_DURATION_MS);
+ mOpenAnimator.setInterpolator(Interpolators.DECELERATE);
+
+ // Create default close animator.
+ mCloseAnimator.play(ObjectAnimator.ofFloat(this, ALPHA, 0));
+ mCloseAnimator.setStartDelay(0);
+ mCloseAnimator.setDuration(HIDE_DURATION_MS);
+ mCloseAnimator.setInterpolator(Interpolators.ACCELERATE);
+ mCloseAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mActivityContext.getDragLayer().removeView(ArrowTipView.this);
+ }
+ });
}
/**
@@ -153,10 +210,10 @@
public ArrowTipView show(
String text, int gravity, int arrowMarginStart, int top, boolean shouldAutoClose) {
((TextView) findViewById(R.id.text)).setText(text);
- ViewGroup parent = mActivity.getDragLayer();
+ ViewGroup parent = mActivityContext.getDragLayer();
parent.addView(this);
- DeviceProfile grid = mActivity.getDeviceProfile();
+ DeviceProfile grid = mActivityContext.getDeviceProfile();
DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
params.gravity = gravity;
@@ -185,14 +242,8 @@
if (shouldAutoClose) {
mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
}
- setAlpha(0);
- animate()
- .alpha(1f)
- .withLayer()
- .setStartDelay(SHOW_DELAY_MS)
- .setDuration(SHOW_DURATION_MS)
- .setInterpolator(Interpolators.DEACCEL)
- .start();
+
+ mOpenAnimator.start();
return this;
}
@@ -273,7 +324,7 @@
*/
@Nullable private ArrowTipView showAtLocation(String text, @Px int arrowXCoord,
@Px int yCoordDownPointingTip, @Px int yCoordUpPointingTip, boolean shouldAutoClose) {
- ViewGroup parent = mActivity.getDragLayer();
+ ViewGroup parent = mActivityContext.getDragLayer();
@Px int parentViewWidth = parent.getWidth();
@Px int parentViewHeight = parent.getHeight();
@Px int maxTextViewWidth = getContext().getResources()
@@ -288,8 +339,10 @@
TextView textView = findViewById(R.id.text);
textView.setText(text);
textView.setMaxWidth(maxTextViewWidth);
- parent.addView(this);
- requestLayout();
+ if (parent.indexOfChild(this) < 0) {
+ parent.addView(this);
+ requestLayout();
+ }
post(() -> {
// Adjust the tooltip horizontally.
@@ -333,14 +386,8 @@
if (shouldAutoClose) {
mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
}
- setAlpha(0);
- animate()
- .alpha(1f)
- .withLayer()
- .setStartDelay(SHOW_DELAY_MS)
- .setDuration(SHOW_DURATION_MS)
- .setInterpolator(Interpolators.DEACCEL)
- .start();
+
+ mOpenAnimator.start();
return this;
}
@@ -351,7 +398,7 @@
Paint arrowPaint = arrowDrawable.getPaint();
@Px int arrowTipRadius = getContext().getResources()
.getDimensionPixelSize(R.dimen.arrow_toast_corner_radius);
- arrowPaint.setColor(ContextCompat.getColor(getContext(), R.color.arrow_tip_view_bg));
+ arrowPaint.setColor(mArrowViewPaintColor);
arrowPaint.setPathEffect(new CornerPathEffect(arrowTipRadius));
mArrowView.setBackground(arrowDrawable);
// Add negative margin so that the rounded corners on base of arrow are not visible.
@@ -378,4 +425,30 @@
super.onConfigurationChanged(newConfig);
close(/* animate= */ false);
}
+
+ /**
+ * Sets a custom animation to run on open of the ArrowTipView.
+ */
+ public void setCustomOpenAnimation(AnimatorSet animator) {
+ mOpenAnimator = animator;
+ }
+
+ /**
+ * Sets a custom animation to run on close of the ArrowTipView.
+ */
+ public void setCustomCloseAnimation(AnimatorSet animator) {
+ mCloseAnimator = animator;
+ }
+
+ private void setTextAlpha(int textAlpha) {
+ if (mTextAlpha != textAlpha) {
+ mTextAlpha = textAlpha;
+ TextView textView = findViewById(R.id.text);
+ textView.setTextColor(textView.getTextColors().withAlpha(mTextAlpha));
+ }
+ }
+
+ private int getTextAlpha() {
+ return mTextAlpha;
+ }
}
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 694dead..87e496e 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -15,9 +15,9 @@
*/
package com.android.launcher3.views;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Utilities.boundToRange;
import static com.android.launcher3.Utilities.mapToRange;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static java.lang.Math.max;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 6b5c8df..41b98c7 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -17,10 +17,10 @@
import static android.view.Gravity.LEFT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Utilities.getBadge;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.Utilities.mapToRange;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
@@ -289,12 +289,14 @@
int width = (int) pos.width();
int height = (int) pos.height();
Object[] tmpObjArray = new Object[1];
+ boolean[] outIsIconThemed = new boolean[1];
if (supportsAdaptiveIcons) {
boolean shouldThemeIcon = btvIcon instanceof FastBitmapDrawable
&& ((FastBitmapDrawable) btvIcon).isThemed();
- drawable = getFullDrawable(l, info, width, height, shouldThemeIcon, tmpObjArray);
+ drawable = getFullDrawable(
+ l, info, width, height, shouldThemeIcon, tmpObjArray, outIsIconThemed);
if (drawable instanceof AdaptiveIconDrawable) {
- badge = getBadge(l, info, tmpObjArray[0]);
+ badge = getBadge(l, info, tmpObjArray[0], outIsIconThemed[0]);
} else {
// The drawable we get back is not an adaptive icon, so we need to use the
// BubbleTextView icon that is already legacy treated.
@@ -306,7 +308,7 @@
drawable = btvIcon;
} else {
drawable = getFullDrawable(l, info, width, height, true /* shouldThemeIcon */,
- tmpObjArray);
+ tmpObjArray, outIsIconThemed);
}
}
}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 4641e31..b62f60d 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -55,7 +55,6 @@
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
@@ -63,8 +62,10 @@
/**
* Popup shown on long pressing an empty space in launcher
+ *
+ * @param <T> The context showing this popup.
*/
-public class OptionsPopupView extends ArrowPopup<Launcher>
+public class OptionsPopupView<T extends Context & ActivityContext> extends ArrowPopup<T>
implements OnClickListener, OnLongClickListener {
// An intent extra to indicate the horizontal scroll of the wallpaper.
@@ -156,21 +157,27 @@
}
}
- public static OptionsPopupView show(ActivityContext launcher, RectF targetRect,
- List<OptionItem> items, boolean shouldAddArrow) {
- return show(launcher, targetRect, items, shouldAddArrow, 0 /* width */);
+ public static <T extends Context & ActivityContext> OptionsPopupView<T> show(
+ ActivityContext activityContext,
+ RectF targetRect,
+ List<OptionItem> items,
+ boolean shouldAddArrow) {
+ return show(activityContext, targetRect, items, shouldAddArrow, 0 /* width */);
}
- public static OptionsPopupView show(ActivityContext launcher, RectF targetRect,
- List<OptionItem> items, boolean shouldAddArrow, int width) {
- OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
- .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
+ public static <T extends Context & ActivityContext> OptionsPopupView<T> show(
+ ActivityContext activityContext,
+ RectF targetRect,
+ List<OptionItem> items,
+ boolean shouldAddArrow,
+ int width) {
+ OptionsPopupView<T> popup = (OptionsPopupView<T>) activityContext.getLayoutInflater()
+ .inflate(R.layout.longpress_options_menu, activityContext.getDragLayer(), false);
popup.mTargetRect = targetRect;
popup.setShouldAddArrow(shouldAddArrow);
for (OptionItem item : items) {
- DeepShortcutView view =
- (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
+ DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
if (width > 0) {
view.getLayoutParams().width = width;
}
@@ -190,14 +197,9 @@
*/
public static ArrayList<OptionItem> getOptions(Launcher launcher) {
ArrayList<OptionItem> options = new ArrayList<>();
- boolean styleWallpaperExists = styleWallpapersExists(launcher);
- int resString = styleWallpaperExists
- ? R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
- int resDrawable = styleWallpaperExists
- ? R.drawable.ic_palette : R.drawable.ic_wallpaper;
options.add(new OptionItem(launcher,
- resString,
- resDrawable,
+ R.string.styles_wallpaper_button_text,
+ R.drawable.ic_palette,
IGNORE,
OptionsPopupView::startWallpaperPicker));
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
@@ -274,12 +276,8 @@
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
.putExtra(EXTRA_WALLPAPER_OFFSET,
launcher.getWorkspace().getWallpaperOffsetForCenterPage())
- .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher");
- if (!styleWallpapersExists(launcher)) {
- intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
- } else {
- intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
- }
+ .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher")
+ .putExtra(EXTRA_WALLPAPER_FLAVOR, "focus_wallpaper");
String pickerPackage = launcher.getString(R.string.wallpaper_picker_package);
if (!TextUtils.isEmpty(pickerPackage)) {
intent.setPackage(pickerPackage);
@@ -290,7 +288,6 @@
static WorkspaceItemInfo placeholderInfo(Intent intent) {
WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo();
placeholderInfo.intent = intent;
- placeholderInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
return placeholderInfo;
}
@@ -323,9 +320,4 @@
this.clickListener = clickListener;
}
}
-
- private static boolean styleWallpapersExists(Context context) {
- return context.getPackageManager().resolveActivity(
- PackageManagerHelper.getStyleWallpapersIntent(context), 0) != null;
- }
}
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index 2460be1..99040ff 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -30,10 +30,10 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.dragndrop.DragLayer;
@@ -175,7 +175,7 @@
.scaleX(1)
.scaleY(1)
.setDuration(SHOW_DURATION_MS)
- .setInterpolator(Interpolators.ACCEL_DEACCEL)
+ .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
.start();
int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(activity,
TIMEOUT_DURATION_MS, FLAG_CONTENT_TEXT | FLAG_CONTENT_CONTROLS);
@@ -190,7 +190,7 @@
.withLayer()
.setStartDelay(0)
.setDuration(HIDE_DURATION_MS)
- .setInterpolator(Interpolators.ACCEL)
+ .setInterpolator(Interpolators.ACCELERATE)
.withEndAction(this::onClosed)
.start();
} else {
diff --git a/src/com/android/launcher3/views/WidgetsEduView.java b/src/com/android/launcher3/views/WidgetsEduView.java
index c2947c7..e70b1cb 100644
--- a/src/com/android/launcher3/views/WidgetsEduView.java
+++ b/src/com/android/launcher3/views/WidgetsEduView.java
@@ -15,9 +15,6 @@
*/
package com.android.launcher3.views;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-
-import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -119,14 +116,11 @@
}
private void animateOpen() {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
return;
}
mIsOpen = true;
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.start();
+ setUpDefaultOpenAnimation().start();
}
/** Shows widget education dialog. */
diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
index 9442734..80b1cdd 100644
--- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
@@ -17,9 +17,7 @@
package com.android.launcher3.widget;
import static com.android.launcher3.Utilities.ATLEAST_R;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import android.animation.PropertyValuesHolder;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Insets;
@@ -130,14 +128,11 @@
}
private void animateOpen() {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
return;
}
mIsOpen = true;
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.start();
+ setUpDefaultOpenAnimation().start();
}
@Override
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 049131e..dcc86a1 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.widget;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
import android.content.Context;
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index bc3889f..340a61e 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -95,6 +95,8 @@
private boolean mTrackingWidgetUpdate = false;
+ private boolean mIsWidgetCachingDisabled = false;
+
public LauncherAppWidgetHostView(Context context) {
super(context);
mLauncher = Launcher.getLauncher(context);
@@ -105,7 +107,7 @@
if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
setOnLightBackground(true);
}
- mColorExtractor = LocalColorExtractor.newInstance(getContext());
+ mColorExtractor = new LocalColorExtractor(); // no-op
}
@Override
@@ -138,6 +140,10 @@
}
}
+ public void setIsWidgetCachingDisabled(boolean isWidgetCachingDisabled) {
+ mIsWidgetCachingDisabled = isWidgetCachingDisabled;
+ }
+
@Override
@TargetApi(Build.VERSION_CODES.Q)
public void updateAppWidget(RemoteViews remoteViews) {
@@ -147,7 +153,8 @@
TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
mTrackingWidgetUpdate = false;
}
- if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
+ if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
+ && !mIsWidgetCachingDisabled) {
mLastRemoteViews = remoteViews;
if (isDeferringUpdates()) {
return;
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 2ca825c..6acc83d 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -275,9 +275,15 @@
protected Bundle getConfigurationActivityOptions(@NonNull BaseDraggingActivity activity,
int widgetId) {
LauncherAppWidgetHostView view = mViews.get(widgetId);
- if (view == null) return null;
+ if (view == null) {
+ return activity.makeDefaultActivityOptions(
+ -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */).toBundle();
+ }
Object tag = view.getTag();
- if (!(tag instanceof ItemInfo)) return null;
+ if (!(tag instanceof ItemInfo)) {
+ return activity.makeDefaultActivityOptions(
+ -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */).toBundle();
+ }
Bundle bundle = activity.getActivityLaunchOptions(view, (ItemInfo) tag).toBundle();
bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY);
return bundle;
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 846dafd..c347939 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -17,9 +17,7 @@
package com.android.launcher3.widget;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -226,15 +224,12 @@
}
private void animateOpen() {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
return;
}
mIsOpen = true;
setupNavBarColor();
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.start();
+ setUpDefaultOpenAnimation().start();
}
@Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 33c4f8d..a4b605c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -23,8 +23,6 @@
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.res.Configuration;
@@ -627,20 +625,13 @@
mContent.setAlpha(0);
setTranslationShift(VERTICAL_START_POSITION);
}
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator
- .setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
- .setInterpolator(AnimationUtils.loadInterpolator(
- getContext(), android.R.interpolator.linear_out_slow_in));
- mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mOpenCloseAnimator.removeListener(this);
- }
- });
+ setUpOpenAnimation(mActivityContext.getDeviceProfile().bottomSheetOpenDuration);
+ Animator animator = mOpenCloseAnimation.getAnimationPlayer();
+ animator.setInterpolator(AnimationUtils.loadInterpolator(
+ getContext(), android.R.interpolator.linear_out_slow_in));
post(() -> {
- mOpenCloseAnimator.start();
+ animator.setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
+ .start();
mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
});
} else {
@@ -774,7 +765,7 @@
super.onCloseComplete();
removeCallbacks(mShowEducationTipTask);
if (mLatestEducationalTip != null) {
- mLatestEducationalTip.close(false);
+ mLatestEducationalTip.close(true);
}
AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
}
@@ -803,13 +794,15 @@
}
// Checks the orientation of the screen
- if (LARGE_SCREEN_WIDGET_PICKER.get()
- && mOrientation != newConfig.orientation
- && mDeviceProfile.isTablet
- && !mDeviceProfile.isTwoPanels) {
+ if (mOrientation != newConfig.orientation) {
mOrientation = newConfig.orientation;
- handleClose(false);
- show(Launcher.getLauncher(getContext()), false);
+ if (LARGE_SCREEN_WIDGET_PICKER.get()
+ && mDeviceProfile.isTablet && !mDeviceProfile.isTwoPanels) {
+ handleClose(false);
+ show(Launcher.getLauncher(getContext()), false);
+ } else {
+ reset();
+ }
}
}
@@ -891,6 +884,19 @@
return false;
}
+ /** Gets the sheet for widget picker, which is used for testing. */
+ @VisibleForTesting
+ public View getSheet() {
+ return mContent;
+ }
+
+ /** Opens the first header in widget picker and scrolls to the top of the RecyclerView. */
+ @VisibleForTesting
+ public void openFirstHeader() {
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.selectFirstHeaderEntry();
+ mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
+ }
+
/** A holder class for holding adapters & their corresponding recycler view. */
final class AdapterHolder {
static final int PRIMARY = 0;
diff --git a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
deleted file mode 100644
index ac0a166..0000000
--- a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.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 kotlin.math.roundToInt
-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
- }
- }
- }
-
- /**
- * Returns the CalculatedWorkspaceSpec for width, based on the available width and the
- * WorkspaceSpecs.
- */
- fun getCalculatedWidthSpec(columns: Int, availableWidth: Int): CalculatedWorkspaceSpec {
- val widthSpec = workspaceWidthSpecList.first { availableWidth <= it.maxAvailableSize }
-
- return CalculatedWorkspaceSpec(availableWidth, columns, widthSpec)
- }
-
- /**
- * Returns the CalculatedWorkspaceSpec for height, based on the available height and the
- * WorkspaceSpecs.
- */
- fun getCalculatedHeightSpec(rows: Int, availableHeight: Int): CalculatedWorkspaceSpec {
- val heightSpec = workspaceHeightSpecList.first { availableHeight <= it.maxAvailableSize }
-
- return CalculatedWorkspaceSpec(availableHeight, rows, heightSpec)
- }
-}
-
-class CalculatedWorkspaceSpec(
- val availableSpace: Int,
- val cells: Int,
- val workspaceSpec: WorkspaceSpec
-) {
- var startPaddingPx: Int = 0
- private set
- var endPaddingPx: Int = 0
- private set
- var gutterPx: Int = 0
- private set
- var cellSizePx: Int = 0
- private set
- init {
- // Calculate all fixed size first
- if (workspaceSpec.startPadding.fixedSize > 0)
- startPaddingPx = workspaceSpec.startPadding.fixedSize.roundToInt()
- if (workspaceSpec.endPadding.fixedSize > 0)
- endPaddingPx = workspaceSpec.endPadding.fixedSize.roundToInt()
- if (workspaceSpec.gutter.fixedSize > 0)
- gutterPx = workspaceSpec.gutter.fixedSize.roundToInt()
- if (workspaceSpec.cellSize.fixedSize > 0)
- cellSizePx = workspaceSpec.cellSize.fixedSize.roundToInt()
-
- // Calculate all available space next
- if (workspaceSpec.startPadding.ofAvailableSpace > 0)
- startPaddingPx =
- (workspaceSpec.startPadding.ofAvailableSpace * availableSpace).roundToInt()
- if (workspaceSpec.endPadding.ofAvailableSpace > 0)
- endPaddingPx = (workspaceSpec.endPadding.ofAvailableSpace * availableSpace).roundToInt()
- if (workspaceSpec.gutter.ofAvailableSpace > 0)
- gutterPx = (workspaceSpec.gutter.ofAvailableSpace * availableSpace).roundToInt()
- if (workspaceSpec.cellSize.ofAvailableSpace > 0)
- cellSizePx = (workspaceSpec.cellSize.ofAvailableSpace * availableSpace).roundToInt()
-
- // Calculate remainder space last
- val gutters = cells - 1
- val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
- val remainderSpace = availableSpace - usedSpace
- if (workspaceSpec.startPadding.ofRemainderSpace > 0)
- startPaddingPx =
- (workspaceSpec.startPadding.ofRemainderSpace * remainderSpace).roundToInt()
- if (workspaceSpec.endPadding.ofRemainderSpace > 0)
- endPaddingPx = (workspaceSpec.endPadding.ofRemainderSpace * remainderSpace).roundToInt()
- if (workspaceSpec.gutter.ofRemainderSpace > 0)
- gutterPx = (workspaceSpec.gutter.ofRemainderSpace * remainderSpace).roundToInt()
- if (workspaceSpec.cellSize.ofRemainderSpace > 0)
- cellSizePx = (workspaceSpec.cellSize.ofRemainderSpace * remainderSpace).roundToInt()
- }
-}
-
-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/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index d6b41c9..599a591 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides;
+import android.app.ActivityOptions;
import android.app.Person;
import android.content.Context;
import android.content.pm.LauncherActivityInfo;
@@ -40,4 +41,11 @@
public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
return Collections.emptyMap();
}
+
+ /**
+ * Creates an ActivityOptions to play fade-out animation on closing targets
+ */
+ public static ActivityOptions createFadeOutAnimOptions(Context context) {
+ return ActivityOptions.makeCustomAnimation(context, 0, android.R.anim.fade_out);
+ }
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index 772a995..b62dbd1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.app.animation.Interpolators.DECELERATE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import android.content.Context;
@@ -80,7 +80,7 @@
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
PageAlphaProvider superPageAlphaProvider = super.getWorkspacePageAlphaProvider(launcher);
- return new PageAlphaProvider(DEACCEL_2) {
+ return new PageAlphaProvider(DECELERATE) {
@Override
public float getPageAlpha(int pageIndex) {
return launcher.getDeviceProfile().isTablet
diff --git a/tests/Android.bp b/tests/Android.bp
index e7f4084..5a52440 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -50,14 +50,13 @@
"src/com/android/launcher3/util/Wait.java",
"src/com/android/launcher3/util/WidgetUtils.java",
"src/com/android/launcher3/util/rule/FailureWatcher.java",
- "src/com/android/launcher3/util/rule/LauncherActivityRule.java",
"src/com/android/launcher3/util/rule/ViewCaptureRule.kt",
"src/com/android/launcher3/util/rule/SamplerRule.java",
"src/com/android/launcher3/util/rule/ScreenRecordRule.java",
"src/com/android/launcher3/util/rule/ShellCommandRule.java",
- "src/com/android/launcher3/util/rule/SimpleActivityRule.java",
"src/com/android/launcher3/util/rule/TestStabilityRule.java",
"src/com/android/launcher3/util/rule/TISBindRule.java",
+ "src/com/android/launcher3/util/viewcapture_analysis/*.java",
"src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
"src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java",
"src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
@@ -87,6 +86,7 @@
"launcher_log_protos_lite",
"truth-prebuilt",
"platform-test-rules",
+ "testables",
],
manifest: "AndroidManifest-common.xml",
platform_apis: true,
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index b170061..ee151bb 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -32,6 +32,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
android:exported="true"
+ android:icon="@drawable/test_widget_no_config_icon"
android:label="No Config">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -41,6 +42,17 @@
</receiver>
<receiver
+ android:name="com.android.launcher3.testcomponent.AppWidgetNoConfigLarge"
+ android:exported="true"
+ android:label="No Config Large">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_no_config_large"/>
+ </receiver>
+
+ <receiver
android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
android:exported="true"
android:label="Hidden widget">
@@ -54,6 +66,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
android:exported="true"
+ android:icon="@drawable/test_widget_with_config_icon"
android:label="With Config">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -65,6 +78,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetWithDialog"
android:exported="true"
+ android:icon="@drawable/test_widget_with_dialog_icon"
android:label="With Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -76,6 +90,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetDynamicColors"
android:exported="true"
+ android:icon="@drawable/test_widget_dynamic_colors_icon"
android:label="Dynamic Colors">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -288,6 +303,28 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
+ <activity-alias android:name="MaxShortcutsActivity"
+ android:label="TestActivityMaxShortcuts"
+ android:exported="true"
+ android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts"
+ android:resource="@xml/max_shortcuts"/>
+ </activity-alias>
+ <activity-alias android:name="SingleShortcutActivity"
+ android:label="TestActivitySingleShortcut"
+ android:exported="true"
+ android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts"
+ android:resource="@xml/single_shortcut"/>
+ </activity-alias>
<activity
android:name="com.android.launcher3.testcomponent.DialogTestActivity"
android:label="Dialog Activity"
@@ -308,6 +345,15 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity-alias android:name="WebSearchActivity"
+ android:label="WebSearchActivity"
+ android:exported="true"
+ android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.WEB_SEARCH" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity-alias>
<!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
<provider
diff --git a/tests/assets/ReorderWidgets/multiple_cell_layouts_no_space_reorder b/tests/assets/ReorderWidgets/multiple_cell_layouts_no_space_reorder
new file mode 100644
index 0000000..c6d65f8
--- /dev/null
+++ b/tests/assets/ReorderWidgets/multiple_cell_layouts_no_space_reorder
@@ -0,0 +1,56 @@
+###################################################################################################
+# This file contains test case composed of the following tags:
+# * # (coments): Lines starting with this character would be ignored.
+# * arguments: is set of words separated by spaces that can later be parsed
+# * board: represent a workspace, the first line is the dimensions of the board width x height (wxh)
+# There are different characters on the board that represent different things:
+# * x: The x character represents spaces that would be ignored, for example it can be used in
+# the first row if we don't know how wide the smartspace is.
+# * i: Represents an icon on the workspace, none in particular just an icon
+# * [a-z]: Represents a widget and it can be any number or character
+# except any other already in use. The whole continuos are of the same character is the
+# area of the widget.
+# * [A-Z]: Represents a folder and number of icons in the folder is represented by the order of
+# letter in the alphabet, A=2, B=3, C=4 ... etc.
+# Test are parsed by CellLayoutTestCaseReader.java and boards are parsed by CellLayoutBoard.java
+###################################################################################################
+# 5x5 Test
+board: 5x5
+xxxxx|aeeee
+--mm-|acccc
+--mm-|acccc
+ggggg|acccc
+ggggg|adddd
+arguments: 7 1
+board: 10x5
+xxxxx|aeeee
+---mm|acccc
+---mm|acccc
+ggggg|acccc
+ggggg|adddd
+# 4x4 Test
+board: 4x4
+xxxx|aeee
+--mm|accc
+--mm|accc
+gggg|accc
+arguments: 5 1
+board: 8x4
+xxxx|aeee
+--mm|accc
+--mm|accc
+gggg|accc
+# 6x5 Test
+board: 6x5
+xxxxxx|aeeeee
+--mm--|accccc
+--mm--|accccc
+gggggg|accccc
+gggggg|addddd
+arguments: 8 1
+board: 12x5
+xxxxxx|aeeeee
+----mm|accccc
+----mm|accccc
+gggggg|accccc
+gggggg|addddd
diff --git a/tests/assets/ReorderWidgets/multiple_cell_layouts_reorder_other_side b/tests/assets/ReorderWidgets/multiple_cell_layouts_reorder_other_side
new file mode 100644
index 0000000..376638e
--- /dev/null
+++ b/tests/assets/ReorderWidgets/multiple_cell_layouts_reorder_other_side
@@ -0,0 +1,56 @@
+###################################################################################################
+# This file contains test case composed of the following tags:
+# * # (coments): Lines starting with this character would be ignored.
+# * arguments: is set of words separated by spaces that can later be parsed
+# * board: represent a workspace, the first line is the dimensions of the board width x height (wxh)
+# There are different characters on the board that represent different things:
+# * x: The x character represents spaces that would be ignored, for example it can be used in
+# the first row if we don't know how wide the smartspace is.
+# * i: Represents an icon on the workspace, none in particular just an icon
+# * [a-z]: Represents a widget and it can be any number or character
+# except any other already in use. The whole continuos are of the same character is the
+# area of the widget.
+# * [A-Z]: Represents a folder and number of icons in the folder is represented by the order of
+# letter in the alphabet, A=2, B=3, C=4 ... etc.
+# Test are parsed by CellLayoutTestCaseReader.java and boards are parsed by CellLayoutBoard.java
+###################################################################################################
+# 5x5 Test
+board: 5x5
+xxxxx|aaaaa
+--mm-|plllh
+--mm-|piiih
+ggggg|piiih
+ggggg|fffff
+arguments: 7 1
+board: 10x5
+xxxxx|aaaaa
+--lll|p-mmh
+---ii|pimmh
+ggggg|piiih
+ggggg|fffff
+# 4x4 Test
+board: 4x4
+xxxx|aaaa
+--mm|pllh
+--mm|piih
+gggg|ffff
+arguments: 5 1
+board: 8x4
+xxxx|aaaa
+--ll|pmmh
+--ii|pmmh
+gggg|ffff
+# 6x5 Test
+board: 6x5
+xxxxxx|aaaaaa
+--mmm-|pllllh
+--mmm-|piiiih
+--mmm-|piiiih
+gggggg|ffffff
+arguments: 8 1
+board: 12x5
+xxxxxx|aaaaaa
+--llll|p-mmmh
+---iii|pimmmh
+---iii|pimmmh
+gggggg|ffffff
\ No newline at end of file
diff --git a/tests/assets/ReorderWidgets/multiple_cell_layouts_simple_reorder b/tests/assets/ReorderWidgets/multiple_cell_layouts_simple_reorder
new file mode 100644
index 0000000..44301d2
--- /dev/null
+++ b/tests/assets/ReorderWidgets/multiple_cell_layouts_simple_reorder
@@ -0,0 +1,56 @@
+###################################################################################################
+# This file contains test case composed of the following tags:
+# * # (coments): Lines starting with this character would be ignored.
+# * arguments: is set of words separated by spaces that can later be parsed
+# * board: represent a workspace, the first line is the dimensions of the board width x height (wxh)
+# There are different characters on the board that represent different things:
+# * x: The x character represents spaces that would be ignored, for example it can be used in
+# the first row if we don't know how wide the smartspace is.
+# * i: Represents an icon on the workspace, none in particular just an icon
+# * [a-z]: Represents a widget and it can be any number or character
+# except any other already in use. The whole continuos are of the same character is the
+# area of the widget.
+# * [A-Z]: Represents a folder and number of icons in the folder is represented by the order of
+# letter in the alphabet, A=2, B=3, C=4 ... etc.
+# Test are parsed by CellLayoutTestCaseReader.java and boards are parsed by CellLayoutBoard.java
+###################################################################################################
+# 5x5 Test
+board: 5x5
+xxxxx|-----
+--mm-|-----
+--mm-|-----
+-----|-----
+-----|-----
+arguments: 8 3
+board: 10x5
+xxxxx|-----
+-----|-----
+-----|-----
+-----|---mm
+-----|---mm
+# 4x4 Test
+board: 4x4
+xxxx|----
+--mm|----
+--mm|----
+----|----
+arguments: 5 3
+board: 8x4
+xxxx|----
+----|----
+----|-mm-
+----|-mm-
+# 6x5 Test
+board: 6x5
+xxxxxx|------
+--m---|------
+------|------
+------|------
+------|------
+arguments: 10 4
+board: 12x5
+xxxxxx|------
+------|------
+------|------
+------|------
+------|----m-
\ No newline at end of file
diff --git a/tests/res/drawable/test_widget_dynamic_colors_icon.xml b/tests/res/drawable/test_widget_dynamic_colors_icon.xml
new file mode 100644
index 0000000..69f6675
--- /dev/null
+++ b/tests/res/drawable/test_widget_dynamic_colors_icon.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#964B00"/>
+ </foreground>
+ <monochrome>
+ <vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
+ </vector>
+ </monochrome>
+</adaptive-icon>
diff --git a/tests/res/drawable/test_widget_no_config_icon.xml b/tests/res/drawable/test_widget_no_config_icon.xml
new file mode 100644
index 0000000..e3d0125
--- /dev/null
+++ b/tests/res/drawable/test_widget_no_config_icon.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#00FFFF"/>
+ </foreground>
+ <monochrome>
+ <vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
+ </vector>
+ </monochrome>
+</adaptive-icon>
diff --git a/tests/res/drawable/test_widget_with_config_icon.xml b/tests/res/drawable/test_widget_with_config_icon.xml
new file mode 100644
index 0000000..98b797b
--- /dev/null
+++ b/tests/res/drawable/test_widget_with_config_icon.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#008000" />
+ </foreground>
+ <monochrome>
+ <vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
+ </vector>
+ </monochrome>
+</adaptive-icon>
diff --git a/tests/res/drawable/test_widget_with_dialog_icon.xml b/tests/res/drawable/test_widget_with_dialog_icon.xml
new file mode 100644
index 0000000..d2879d2
--- /dev/null
+++ b/tests/res/drawable/test_widget_with_dialog_icon.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#800080"/>
+ </foreground>
+ <monochrome>
+ <vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
+ </vector>
+ </monochrome>
+</adaptive-icon>
diff --git a/tests/res/raw/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt
deleted file mode 100644
index 603dbe3..0000000
--- a/tests/res/raw/cache_data_updated_task_data.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-# Model data used by CacheDataUpdatedTaskTest
-
-classMap s com.android.launcher3.model.data.WorkspaceItemInfo
-
-# Items for the BgDataModel
-
-# App shortcuts
-bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
-bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
-bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
-bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
-
-# Auto install app shortcut
-bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
-bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
-
-# Custom shortcuts
-bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
-bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
-
-# Restored custom shortcut
-bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
-bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
-
-allApps componentName=app1/class1 intent=component=app1/class1
-allApps componentName=app1/class2 intent=component=app1/class2
-allApps componentName=app2/class1 intent=component=app2/class1
-allApps componentName=app2/class2 intent=component=app2/class2
\ No newline at end of file
diff --git a/tests/res/raw/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt
deleted file mode 100644
index e82ea9d..0000000
--- a/tests/res/raw/package_install_state_change_task_data.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-# Model data used by PackageInstallStateChangeTaskTest
-
-classMap s com.android.launcher3.model.data.WorkspaceItemInfo
-classMap w com.android.launcher3.model.data.LauncherAppWidgetInfo
-
-# Items for the BgDataModel
-
-# App shortcuts
-bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
-bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
-bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
-bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
-
-# Promise icons for app3
-bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
-bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
-bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
-
-# Promise icon for app4
-bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
-
-# Widget
-bgItem w providerName=app4/provider1 id=9
-bgItem w providerName=app5/provider1 id=10
\ No newline at end of file
diff --git a/tests/res/raw/widgets_predication_update_task_data.txt b/tests/res/raw/widgets_predication_update_task_data.txt
deleted file mode 100644
index 941d195..0000000
--- a/tests/res/raw/widgets_predication_update_task_data.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-# Model data used by WidgetsPredictionUpdateTasksTest
-
-classMap s com.android.launcher3.model.data.WorkspaceItemInfo
-classMap w com.android.launcher3.model.data.LauncherAppWidgetInfo
-
-# Items for the BgDataModel
-
-# App shortcuts
-bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
-bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
-bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
-bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
-
-# Promise icons for app3
-bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
-bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
-bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
-
-# Promise icon for app4
-bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
-
-# Widget
-bgItem w providerName=app4/provider1 id=9
-bgItem w providerName=app5/provider1 id=10
\ No newline at end of file
diff --git a/tests/res/values/attrs.xml b/tests/res/values/attrs.xml
index 2310d9e..e5ee064 100644
--- a/tests/res/values/attrs.xml
+++ b/tests/res/values/attrs.xml
@@ -18,7 +18,7 @@
<!-- Attributes have to be copied to test for correct parsing of files -->
<resources>
<!-- Responsive grids attributes -->
- <declare-styleable name="WorkspaceSpec">
+ <declare-styleable name="ResponsiveSpec">
<attr name="specType" format="integer">
<enum name="height" value="0" />
<enum name="width" value="1" />
@@ -26,10 +26,26 @@
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
- <declare-styleable name="SpecSize">
+ <declare-styleable name="WorkspaceSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
+ <declare-styleable name="FolderSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
+ <declare-styleable name="AllAppsSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
+ <declare-styleable name="SizeSpec">
<attr name="fixedSize" format="dimension" />
<attr name="ofAvailableSpace" format="float" />
<attr name="ofRemainderSpace" format="float" />
+ <attr name="matchWorkspace" format="boolean" />
+ <attr name="maxSize" format="dimension" />
</declare-styleable>
-
</resources>
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
index 0ad87fb..54ade56 100644
--- a/tests/res/values/strings.xml
+++ b/tests/res/values/strings.xml
@@ -3,4 +3,5 @@
<string name="shortcut1" translatable="false">Shortcut 1</string>
<string name="shortcut2" translatable="false">Shortcut 2</string>
<string name="shortcut3" translatable="false">Shortcut 3</string>
+ <string name="shortcut4" translatable="false">Shortcut 4</string>
</resources>
diff --git a/tests/res/xml/appwidget_no_config_large.xml b/tests/res/xml/appwidget_no_config_large.xml
new file mode 100644
index 0000000..b3b69d9
--- /dev/null
+++ b/tests/res/xml/appwidget_no_config_large.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="1dp"
+ android:minHeight="1dp"
+ android:minResizeWidth="1dp"
+ android:maxResizeWidth="3000dp"
+ android:targetCellHeight="1"
+ android:targetCellWidth="5"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/test_layout_appwidget_red"
+ android:previewLayout="@layout/test_layout_appwidget_red"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_all_apps_file_case_1.xml b/tests/res/xml/invalid_all_apps_file_case_1.xml
new file mode 100644
index 0000000..6fd35b1
--- /dev/null
+++ b/tests/res/xml/invalid_all_apps_file_case_1.xml
@@ -0,0 +1,36 @@
+<?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.
+ -->
+<allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <allAppsSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <!-- missing startPadding -->
+ <endPadding launcher:fixedSize="0dp" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+ <allAppsSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:matchWorkspace="true" />
+ <endPadding launcher:matchWorkspace="true" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+</allAppsSpecs>
+
diff --git a/tests/res/xml/invalid_all_apps_file_case_2.xml b/tests/res/xml/invalid_all_apps_file_case_2.xml
new file mode 100644
index 0000000..de9c1ac
--- /dev/null
+++ b/tests/res/xml/invalid_all_apps_file_case_2.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+<allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <allAppsSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="0dp" />
+ <endPadding launcher:fixedSize="0dp" />
+ <!-- more than 1 value in one tag -->
+ <gutter
+ launcher:matchWorkspace="true"
+ launcher:fixedSize="16dp" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+ <allAppsSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:matchWorkspace="true" />
+ <endPadding launcher:matchWorkspace="true" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+</allAppsSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_all_apps_file_case_3.xml b/tests/res/xml/invalid_all_apps_file_case_3.xml
new file mode 100644
index 0000000..7af0af4
--- /dev/null
+++ b/tests/res/xml/invalid_all_apps_file_case_3.xml
@@ -0,0 +1,37 @@
+<?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.
+ -->
+<allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <allAppsSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="0dp" />
+ <endPadding launcher:fixedSize="0dp" />
+ <gutter launcher:matchWorkspace="true" />
+ <!-- value bigger than 1 -->
+ <cellSize launcher:ofRemainderSpace="1.001" />
+ </allAppsSpec>
+
+ <allAppsSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:matchWorkspace="true" />
+ <endPadding launcher:matchWorkspace="true" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+</allAppsSpecs>
+
diff --git a/tests/res/xml/invalid_folders_specs_1.xml b/tests/res/xml/invalid_folders_specs_1.xml
new file mode 100644
index 0000000..0864249
--- /dev/null
+++ b/tests/res/xml/invalid_folders_specs_1.xml
@@ -0,0 +1,40 @@
+<?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.
+ -->
+
+<!-- Tablet - 6x5 portrait -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+ <!-- missing startPadding -->
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:matchWorkspace="true" />
+ </folderSpec>
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="102dp" />
+ </folderSpec>
+
+ <!-- Height spec is fixed -->
+ <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="24dp" />
+ <!-- mapped to footer height size -->
+ <endPadding launcher:fixedSize="64dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="104dp" />
+ </folderSpec>
+</folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_2.xml b/tests/res/xml/invalid_folders_specs_2.xml
new file mode 100644
index 0000000..0b7dd62
--- /dev/null
+++ b/tests/res/xml/invalid_folders_specs_2.xml
@@ -0,0 +1,43 @@
+<?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.
+ -->
+
+<!-- Tablet - 6x5 portrait -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <!-- more than 1 value in one tag -->
+ <gutter
+ launcher:ofAvailableSpace="0.0125"
+ launcher:fixedSize="16dp" />
+ <cellSize launcher:matchWorkspace="true" />
+ </folderSpec>
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="102dp" />
+ </folderSpec>
+
+ <!-- Height spec is fixed -->
+ <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="24dp" />
+ <!-- mapped to footer height size -->
+ <endPadding launcher:fixedSize="64dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="104dp" />
+ </folderSpec>
+</folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_3.xml b/tests/res/xml/invalid_folders_specs_3.xml
new file mode 100644
index 0000000..83fd3e1
--- /dev/null
+++ b/tests/res/xml/invalid_folders_specs_3.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+
+<!-- Tablet - 6x5 portrait - More the one value first gutter -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <!-- value bigger than 1 -->
+ <cellSize launcher:ofRemainderSpace="1.001" />
+ </folderSpec>
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="102dp" />
+ </folderSpec>
+
+ <!-- Height spec is fixed -->
+ <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="24dp" />
+ <!-- mapped to footer height size -->
+ <endPadding launcher:fixedSize="64dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="104dp" />
+ </folderSpec>
+</folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_4.xml b/tests/res/xml/invalid_folders_specs_4.xml
new file mode 100644
index 0000000..2d8c730
--- /dev/null
+++ b/tests/res/xml/invalid_folders_specs_4.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<!-- missing height spec -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:matchWorkspace="true" />
+ </folderSpec>
+</folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_5.xml b/tests/res/xml/invalid_folders_specs_5.xml
new file mode 100644
index 0000000..b4f1f4d
--- /dev/null
+++ b/tests/res/xml/invalid_folders_specs_5.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+<!-- missing breakpoints > 800dp -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:matchWorkspace="true" />
+ </folderSpec>
+
+ <!-- Height spec is fixed -->
+ <folderSpec launcher:specType="height" launcher:maxAvailableSize="800dp">
+ <startPadding launcher:fixedSize="24dp" />
+ <!-- mapped to footer height size -->
+ <endPadding launcher:fixedSize="64dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="104dp" />
+ </folderSpec>
+</folderSpecs>
diff --git a/tests/res/xml/invalid_hotseat_file_case_1.xml b/tests/res/xml/invalid_hotseat_file_case_1.xml
new file mode 100644
index 0000000..fcbc5ea
--- /dev/null
+++ b/tests/res/xml/invalid_hotseat_file_case_1.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="847dp">
+ <hotseatQsbSpace launcher:ofAvailableSpace="1" />
+ </hotseatSpec>
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <hotseatQsbSpace launcher:fixedSize="36dp" />
+ </hotseatSpec>
+
+</hotseatSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_workspace_file_case_4.xml b/tests/res/xml/invalid_workspace_file_case_4.xml
new file mode 100644
index 0000000..9e74c85
--- /dev/null
+++ b/tests/res/xml/invalid_workspace_file_case_4.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" />
+ <!-- value in workspace spec using matchWorkspace -->
+ <gutter
+ launcher:matchWorkspace="true" />
+ <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/max_shortcuts.xml b/tests/res/xml/max_shortcuts.xml
new file mode 100644
index 0000000..312a24c
--- /dev/null
+++ b/tests/res/xml/max_shortcuts.xml
@@ -0,0 +1,39 @@
+<?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.
+ -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="max_shortcut1"
+ android:icon="@drawable/test_theme_icon"
+ android:shortcutShortLabel="@string/shortcut1">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut_max"/>
+ </shortcut>
+ <shortcut
+ android:shortcutId="max_shortcut2"
+ android:shortcutShortLabel="@string/shortcut2">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut_max"/>
+ </shortcut>
+ <shortcut
+ android:shortcutId="max_shortcut3"
+ android:shortcutShortLabel="@string/shortcut3">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut_max"/>
+ </shortcut>
+ <shortcut
+ android:shortcutId="max_shortcut4"
+ android:shortcutShortLabel="@string/shortcut4">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut_max"/>
+ </shortcut>
+</shortcuts>
diff --git a/tests/res/xml/shortcuts.xml b/tests/res/xml/shortcuts.xml
index 94e8edd..2ba9d7f 100644
--- a/tests/res/xml/shortcuts.xml
+++ b/tests/res/xml/shortcuts.xml
@@ -1,4 +1,19 @@
<?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.
+ -->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="shortcut1_themed"
diff --git a/tests/res/xml/single_shortcut.xml b/tests/res/xml/single_shortcut.xml
new file mode 100644
index 0000000..e8d938f
--- /dev/null
+++ b/tests/res/xml/single_shortcut.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="single_shortcut_themed"
+ android:icon="@drawable/test_theme_icon"
+ android:shortcutShortLabel="@string/shortcut1">
+ <intent android:action="com.android.launcher3.intent.action.test_shortcut_single"/>
+ </shortcut>
+</shortcuts>
diff --git a/tests/res/xml/valid_all_apps_file.xml b/tests/res/xml/valid_all_apps_file.xml
new file mode 100644
index 0000000..0be55d1
--- /dev/null
+++ b/tests/res/xml/valid_all_apps_file.xml
@@ -0,0 +1,36 @@
+<?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.
+ -->
+
+<allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <allAppsSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="0dp" />
+ <endPadding launcher:fixedSize="0dp" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+ <allAppsSpec
+ launcher:specType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:matchWorkspace="true" />
+ <endPadding launcher:matchWorkspace="true" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+</allAppsSpecs>
diff --git a/tests/res/xml/valid_folders_specs.xml b/tests/res/xml/valid_folders_specs.xml
new file mode 100644
index 0000000..0c45544
--- /dev/null
+++ b/tests/res/xml/valid_folders_specs.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:matchWorkspace="true" />
+ </folderSpec>
+ <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="102dp" />
+ </folderSpec>
+
+ <!-- Height spec is fixed -->
+ <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="24dp" />
+ <!-- mapped to footer height size -->
+ <endPadding launcher:fixedSize="64dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:matchWorkspace="true" />
+ </folderSpec>
+</folderSpecs>
diff --git a/tests/res/xml/valid_hotseat_file.xml b/tests/res/xml/valid_hotseat_file.xml
new file mode 100644
index 0000000..c7f52e8
--- /dev/null
+++ b/tests/res/xml/valid_hotseat_file.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="847dp">
+ <hotseatQsbSpace launcher:fixedSize="24dp" />
+ </hotseatSpec>
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <hotseatQsbSpace launcher:fixedSize="36dp" />
+ </hotseatSpec>
+
+</hotseatSpecs>
\ No newline at end of file
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 2ac0dd7..825b3dc 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -39,7 +39,6 @@
public static final int HINT_STATE_TWO_BUTTON_ORDINAL = 8;
public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 9;
public static final int EDIT_MODE_STATE_ORDINAL = 10;
- public static final String TAPL_EVENTS_TAG = "TaplEvents";
public static final String SEQUENCE_MAIN = "Main";
public static final String SEQUENCE_TIS = "TIS";
public static final String SEQUENCE_PILFER = "Pilfer";
@@ -110,6 +109,7 @@
public static final String REQUEST_GET_TEST_EVENTS = "get-test-events";
public static final String REQUEST_GET_HAD_NONTEST_EVENTS = "get-had-nontest-events";
public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging";
+ public static final String REQUEST_REINITIALIZE_DATA = "reinitialize-data";
public static final String REQUEST_CLEAR_DATA = "clear-data";
public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
public static final String REQUEST_IS_TABLET = "is-tablet";
@@ -152,15 +152,20 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
- public static final String WORK_TAB_MISSING = "b/243688989";
public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
- public static final String FLAKY_ACTIVITY_COUNT = "b/260260325";
+ public static final String FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP = "b/286084688";
+ public static final String ICON_MISSING = "b/282963545";
+ public static final String LAUNCH_SPLIT_PAIR = "b/288939273";
+ public static final String OVERVIEW_OVER_HOME = "b/279059025";
+ public static final String INCORRECT_HOME_STATE = "b/293191790";
public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
public static final String REQUEST_IS_EMULATE_DISPLAY_RUNNING = "is-emulate-display-running";
public static final String REQUEST_EMULATE_PRINT_DEVICE = "emulate-print-device";
+ public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
+
/** Logs {@link Log#d(String, String)} if {@link #sDebugTracing} is true. */
public static void testLogD(String tag, String message) {
if (!sDebugTracing) {
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 3de4d55..dd79ca8 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -22,11 +22,16 @@
import android.util.DisplayMetrics
import android.view.Surface
import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.testing.shared.ResourceUtils
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 java.io.BufferedReader
+import java.io.File
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlin.math.max
import kotlin.math.min
import org.junit.After
@@ -154,41 +159,55 @@
}
protected fun initializeVarsForTwoPanel(
- deviceTabletSpec: DeviceSpec,
- deviceSpec: DeviceSpec,
+ deviceSpecUnfolded: DeviceSpec,
+ deviceSpecFolded: DeviceSpec,
isLandscape: Boolean = false,
- isGestureMode: Boolean = true
+ isGestureMode: Boolean = true,
+ isFolded: Boolean = false
) {
- val (tabletNaturalX, tabletNaturalY) = deviceTabletSpec.naturalSize
- val tabletWindowsBounds =
- tabletWindowsBounds(deviceTabletSpec, tabletNaturalX, tabletNaturalY)
- val tabletDisplayInfo =
+ val (unfoldedNaturalX, unfoldedNaturalY) = deviceSpecUnfolded.naturalSize
+ val unfoldedWindowsBounds =
+ tabletWindowsBounds(deviceSpecUnfolded, unfoldedNaturalX, unfoldedNaturalY)
+ val unfoldedDisplayInfo =
CachedDisplayInfo(
- Point(tabletNaturalX, tabletNaturalY),
+ Point(unfoldedNaturalX, unfoldedNaturalY),
Surface.ROTATION_0,
Rect(0, 0, 0, 0)
)
- val (phoneNaturalX, phoneNaturalY) = deviceSpec.naturalSize
- val phoneWindowsBounds =
- phoneWindowsBounds(deviceSpec, isGestureMode, phoneNaturalX, phoneNaturalY)
- val phoneDisplayInfo =
+ val (foldedNaturalX, foldedNaturalY) = deviceSpecFolded.naturalSize
+ val foldedWindowsBounds =
+ phoneWindowsBounds(deviceSpecFolded, isGestureMode, foldedNaturalX, foldedNaturalY)
+ val foldedDisplayInfo =
CachedDisplayInfo(
- Point(phoneNaturalX, phoneNaturalY),
+ Point(foldedNaturalX, foldedNaturalY),
Surface.ROTATION_0,
Rect(0, 0, 0, 0)
)
val perDisplayBoundsCache =
- mapOf(tabletDisplayInfo to tabletWindowsBounds, phoneDisplayInfo to phoneWindowsBounds)
+ mapOf(
+ unfoldedDisplayInfo to unfoldedWindowsBounds,
+ foldedDisplayInfo to foldedWindowsBounds
+ )
- initializeCommonVars(
- perDisplayBoundsCache,
- tabletDisplayInfo,
- rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
- isGestureMode,
- densityDpi = deviceTabletSpec.densityDpi
- )
+ if (isFolded) {
+ initializeCommonVars(
+ perDisplayBoundsCache = perDisplayBoundsCache,
+ displayInfo = foldedDisplayInfo,
+ rotation = if (isLandscape) Surface.ROTATION_90 else Surface.ROTATION_0,
+ isGestureMode = isGestureMode,
+ densityDpi = deviceSpecFolded.densityDpi
+ )
+ } else {
+ initializeCommonVars(
+ perDisplayBoundsCache = perDisplayBoundsCache,
+ displayInfo = unfoldedDisplayInfo,
+ rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
+ isGestureMode = isGestureMode,
+ densityDpi = deviceSpecUnfolded.densityDpi
+ )
+ }
}
private fun phoneWindowsBounds(
@@ -196,7 +215,7 @@
isGestureMode: Boolean,
naturalX: Int,
naturalY: Int
- ): Array<WindowBounds> {
+ ): List<WindowBounds> {
val buttonsNavHeight = Utilities.dpToPx(48f, deviceSpec.densityDpi)
val rotation0Insets =
@@ -231,7 +250,7 @@
if (isGestureMode) deviceSpec.gesturePx else 0
)
- return arrayOf(
+ return listOf(
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),
@@ -243,11 +262,11 @@
deviceSpec: DeviceSpec,
naturalX: Int,
naturalY: Int
- ): Array<WindowBounds> {
+ ): List<WindowBounds> {
val naturalInsets = Rect(0, deviceSpec.statusBarNaturalPx, 0, 0)
val rotatedInsets = Rect(0, deviceSpec.statusBarRotatedPx, 0, 0)
- return arrayOf(
+ return listOf(
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),
@@ -256,7 +275,7 @@
}
private fun initializeCommonVars(
- perDisplayBoundsCache: Map<CachedDisplayInfo, Array<WindowBounds>>,
+ perDisplayBoundsCache: Map<CachedDisplayInfo, List<WindowBounds>>,
displayInfo: CachedDisplayInfo,
rotation: Int,
isGestureMode: Boolean = true,
@@ -287,4 +306,27 @@
whenever(displayController.info).thenReturn(info)
whenever(displayController.isTransientTaskbar).thenReturn(isGestureMode)
}
+
+ /** Create a new dump of DeviceProfile, saves to a file in the device and returns it */
+ protected fun dump(context: Context, dp: DeviceProfile, fileName: String): String {
+ val stringWriter = StringWriter()
+ PrintWriter(stringWriter).use { dp.dump(context, "", it) }
+ return stringWriter.toString().also { content -> writeToDevice(context, fileName, content) }
+ }
+
+ /** Read a file from assets/ and return it as a string */
+ protected fun readDumpFromAssets(context: Context, fileName: String): String =
+ context.assets.open("dumpTests/$fileName").bufferedReader().use(BufferedReader::readText)
+
+ private fun writeToDevice(context: Context, fileName: String, content: String) {
+ File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName).writeText(content)
+ }
+
+ protected fun Float.dpToPx(): Float {
+ return ResourceUtils.pxFromDp(this, context!!.resources.displayMetrics).toFloat()
+ }
+
+ protected fun Int.dpToPx(): Int {
+ return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
+ }
}
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
index 3c2b49a..ff667e6 100644
--- a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
@@ -103,6 +103,8 @@
public static final char IGNORE = 'x';
// The cells marked by this will be filled by app icons
public static final char ICON = 'i';
+ // The cells marked by FOLDER will be filled by folders with 27 app icons inside
+ public static final char FOLDER = 'Z';
// Empty space
public static final char EMPTY = '-';
// Widget that will be saved as "main widget" for easier retrieval
@@ -139,9 +141,14 @@
return this.mType == CellType.IGNORE;
}
+ boolean contains(int x, int y) {
+ return mBounds.contains(x, y);
+ }
+
@Override
public String toString() {
- return "WidgetRect type = " + mType + " bounds = " + mBounds.toString();
+ return "WidgetRect type = " + mType + " x = " + getCellX() + " | y " + getCellY()
+ + " xs = " + getSpanX() + " ys = " + getSpanY();
}
}
@@ -171,6 +178,25 @@
}
}
+ public static class FolderPoint {
+ public Point coord;
+ public char mType;
+
+ public FolderPoint(Point coord, char type) {
+ this.coord = coord;
+ mType = type;
+ }
+
+ /**
+ * [A-Z]: Represents a folder and number of icons in the folder is represented by
+ * the order of letter in the alphabet, A=2, B=3, C=4 ... etc.
+ */
+ public int getNumberIconsInside() {
+ return (mType - 'A') + 2;
+ }
+ }
+
+
private HashSet<Character> mUsedWidgetTypes = new HashSet<>();
static final int INFINITE = 99999;
@@ -181,6 +207,7 @@
Map<Character, WidgetRect> mWidgetsMap = new HashMap<>();
List<IconPoint> mIconPoints = new ArrayList<>();
+ List<FolderPoint> mFolderPoints = new ArrayList<>();
WidgetRect mMain = null;
@@ -205,6 +232,17 @@
}
}
+ public boolean pointInsideRect(int x, int y, WidgetRect rect) {
+ Boolean isXInRect = x >= rect.getCellX() && x < rect.getCellX() + rect.getSpanX();
+ Boolean isYInRect = y >= rect.getCellY() && y < rect.getCellY() + rect.getSpanY();
+ return isXInRect && isYInRect;
+ }
+
+ public WidgetRect getWidgetAt(int x, int y) {
+ return mWidgetsRects.stream()
+ .filter(widgetRect -> pointInsideRect(x, y, widgetRect)).findFirst().orElse(null);
+ }
+
public List<WidgetRect> getWidgets() {
return mWidgetsRects;
}
@@ -213,6 +251,10 @@
return mIconPoints;
}
+ public List<FolderPoint> getFolders() {
+ return mFolderPoints;
+ }
+
public WidgetRect getMain() {
return mMain;
}
@@ -248,6 +290,17 @@
}
return true;
}).collect(Collectors.toList());
+
+ // Remove overlapping folders and remove them from the board
+ mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
+ int x = folderPoint.coord.x;
+ int y = folderPoint.coord.y;
+ if (rect.contains(x, y)) {
+ mWidget[x][y] = '-';
+ return false;
+ }
+ return true;
+ }).collect(Collectors.toList());
}
private void removeOverlappingItems(Point p) {
@@ -269,6 +322,17 @@
}
return true;
}).collect(Collectors.toList());
+
+ // Remove overlapping folders and remove them from the board
+ mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
+ int x = folderPoint.coord.x;
+ int y = folderPoint.coord.y;
+ if (p.x == x && p.y == y) {
+ mWidget[x][y] = '-';
+ return false;
+ }
+ return true;
+ }).collect(Collectors.toList());
}
private char getNextWidgetType() {
@@ -373,6 +437,18 @@
return iconPoints;
}
+ private static List<FolderPoint> getFolderPoints(char[][] board) {
+ List<FolderPoint> folderPoints = new ArrayList<>();
+ for (int x = 0; x < board.length; x++) {
+ for (int y = 0; y < board[0].length; y++) {
+ if (isFolder(board[x][y])) {
+ folderPoints.add(new FolderPoint(new Point(x, y), board[x][y]));
+ }
+ }
+ }
+ return folderPoints;
+ }
+
public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) {
for (CellLayoutBoard board : boards) {
WidgetRect main = board.getMain();
@@ -383,6 +459,17 @@
return null;
}
+ public static WidgetRect getWidgetIn(List<CellLayoutBoard> boards, int x, int y) {
+ for (CellLayoutBoard board : boards) {
+ WidgetRect main = board.getWidgetAt(x, y);
+ if (main != null) {
+ return main;
+ }
+ x -= board.mWidth;
+ }
+ return null;
+ }
+
public static CellLayoutBoard boardFromString(String boardStr) {
String[] lines = boardStr.split("\n");
CellLayoutBoard board = new CellLayoutBoard();
@@ -406,6 +493,7 @@
board.mWidgetsMap.put(widgetRect.mType, widgetRect);
});
board.mIconPoints = getIconPoints(board.mWidget);
+ board.mFolderPoints = getFolderPoints(board.mWidget);
return board;
}
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java b/tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
index e33a304..419cb3d 100644
--- a/tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
@@ -63,8 +63,8 @@
}
public static class Board extends TestSection {
- Point gridSize;
- String board;
+ public Point gridSize;
+ public String board;
public Board(Point gridSize, String board) {
super(State.BOARD);
@@ -127,7 +127,7 @@
}
}
- List<TestSection> parse() {
+ public List<TestSection> parse() {
List<TestSection> sections = new ArrayList<>();
String[] lines = mTest.split("\n");
Iterator<String> it = Arrays.stream(lines).iterator();
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java b/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
index 0d2f252..86a7bd3 100644
--- a/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
@@ -42,8 +42,8 @@
params.getCellX(), params.getCellY(),
launcher.getWorkspace().getIdForScreen(cellLayout), CONTAINER_DESKTOP);
int screenId = pos.screenId;
- if (screenId >= boards.size() - 1) {
- boards.add(new CellLayoutBoard());
+ for (int j = boards.size(); j <= screenId; j++) {
+ boards.add(new CellLayoutBoard(cellLayout.getCountX(), cellLayout.getCountY()));
}
CellLayoutBoard board = boards.get(screenId);
// is icon
@@ -51,7 +51,7 @@
board.addIcon(pos.cellX, pos.cellY);
} else {
// is widget
- board.addWidget(params.getCellX(), params.getCellY(), params.cellHSpan,
+ board.addWidget(pos.cellX, pos.cellY, params.cellHSpan,
params.cellVSpan);
}
}
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index 8ce932d..bf7a21c 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -15,62 +15,116 @@
*/
package com.android.launcher3.celllayout;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
-import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
+import androidx.test.uiautomator.UiDevice;
+
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.util.ContentWriter;
import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
+import java.util.function.Supplier;
public class FavoriteItemsTransaction {
- private ArrayList<ItemInfo> mItemsToSubmit;
+ private ArrayList<Supplier<ItemInfo>> mItemsToSubmit;
private Context mContext;
- private ContentResolver mResolver;
- public AbstractLauncherUiTest mTest;
- public FavoriteItemsTransaction(Context context, AbstractLauncherUiTest test) {
+ public FavoriteItemsTransaction(Context context) {
mItemsToSubmit = new ArrayList<>();
mContext = context;
- mResolver = mContext.getContentResolver();
- mTest = test;
}
- public FavoriteItemsTransaction addItem(ItemInfo itemInfo) {
+ public FavoriteItemsTransaction addItem(Supplier<ItemInfo> itemInfo) {
this.mItemsToSubmit.add(itemInfo);
return this;
}
- public FavoriteItemsTransaction removeLast() {
- this.mItemsToSubmit.remove(this.mItemsToSubmit.size() - 1);
- return this;
- }
-
/**
* Commits all the ItemInfo into the database of Favorites
**/
- public void commit() throws ExecutionException, InterruptedException {
- List<ContentValues> values = new ArrayList<>();
- for (ItemInfo item : this.mItemsToSubmit) {
- ContentWriter writer = new ContentWriter(mContext);
- item.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites._ID, item.id);
- values.add(writer.getValues(mContext));
- }
- // Submit the icons to the database in the model thread to prevent race conditions
- MODEL_EXECUTOR.submit(() -> mResolver.bulkInsert(LauncherSettings.Favorites.CONTENT_URI,
- values.toArray(new ContentValues[0]))).get();
- // Reload the state of the Launcher
- MAIN_EXECUTOR.submit(() -> LauncherAppState.getInstance(
- mContext).getModel().forceReload()).get();
+ public void commit() {
+ LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+ // Load the model once so that there is no pending migration:
+ loadModelSync(model);
+
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ ModelDbController controller = model.getModelDbController();
+ // Migrate any previous data so that the DB state is correct
+ controller.tryMigrateDB();
+
+ // Create DB again to load fresh data
+ controller.createEmptyDB();
+ controller.clearEmptyDbFlag();
+
+ // Add new data
+ try (SQLiteTransaction transaction = controller.newTransaction()) {
+ int count = mItemsToSubmit.size();
+ ArrayList<ItemInfo> containerItems = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ ContentWriter writer = new ContentWriter(mContext);
+ ItemInfo item = mItemsToSubmit.get(i).get();
+
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ FolderInfo folderInfo = (FolderInfo) item;
+ for (ItemInfo itemInfo : folderInfo.contents) {
+ itemInfo.container = i;
+ containerItems.add(itemInfo);
+ }
+ }
+
+ item.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites._ID, i);
+ controller.insert(TABLE_NAME, writer.getValues(mContext));
+ }
+
+ for (int i = 0; i < containerItems.size(); i++) {
+ ContentWriter writer = new ContentWriter(mContext);
+ ItemInfo item = containerItems.get(i);
+ item.onAddToDatabase(writer);
+ writer.put(LauncherSettings.Favorites._ID, count + i);
+ controller.insert(TABLE_NAME, writer.getValues(mContext));
+ }
+ transaction.commit();
+ }
+ });
+
+ // Reload model
+ runOnExecutorSync(MAIN_EXECUTOR, model::forceReload);
+ loadModelSync(model);
+ }
+
+ private void loadModelSync(LauncherModel model) {
+ Callbacks mockCb = new Callbacks() { };
+ runOnExecutorSync(MAIN_EXECUTOR, () -> model.addCallbacksAndLoad(mockCb));
+ runOnExecutorSync(MODEL_EXECUTOR, () -> { });
+
+ runOnExecutorSync(MAIN_EXECUTOR, () -> { });
+ runOnExecutorSync(MAIN_EXECUTOR, () -> model.removeCallbacks(mockCb));
+ }
+
+ /**
+ * Commits the transaction and waits for home load
+ */
+ public void commitAndLoadHome(LauncherInstrumentation inst) {
+ commit();
+
+ // Launch the home activity
+ UiDevice.getInstance(getInstrumentation()).pressHome();
+ inst.waitForLauncherInitialized();
}
}
diff --git a/tests/src/com/android/launcher3/celllayout/MultipleCellLayoutsSimpleReorder.java b/tests/src/com/android/launcher3/celllayout/MultipleCellLayoutsSimpleReorder.java
deleted file mode 100644
index 706c1a7..0000000
--- a/tests/src/com/android/launcher3/celllayout/MultipleCellLayoutsSimpleReorder.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.celllayout.testcases;
-
-import android.graphics.Point;
-
-import com.android.launcher3.celllayout.ReorderTestCase;
-
-import java.util.Map;
-
-/**
- * The grids represent the workspace to be build by TestWorkspaceBuilder, to see what each character
- * in the board mean refer to {@code CellType}
- */
-public class MultipleCellLayoutsSimpleReorder {
-
- /** 5x5 Test
- **/
- private static final String START_BOARD_STR_5x5 = ""
- + "xxxxx|-----\n"
- + "--mm-|-----\n"
- + "--mm-|-----\n"
- + "-----|-----\n"
- + "-----|-----";
- private static final Point MOVE_TO_5x5 = new Point(8, 3);
- private static final String END_BOARD_STR_5x5 = ""
- + "xxxxx|-----\n"
- + "-----|-----\n"
- + "-----|-----\n"
- + "-----|---mm\n"
- + "-----|---mm";
- private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
- MOVE_TO_5x5,
- END_BOARD_STR_5x5);
-
- /** 4x4 Test
- **/
- private static final String START_BOARD_STR_4x4 = ""
- + "xxxx|----\n"
- + "--mm|----\n"
- + "--mm|----\n"
- + "----|----";
- private static final Point MOVE_TO_4x4 = new Point(5, 3);
- private static final String END_BOARD_STR_4x4 = ""
- + "xxxx|----\n"
- + "----|----\n"
- + "----|-mm-\n"
- + "----|-mm-";
- private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
- MOVE_TO_4x4,
- END_BOARD_STR_4x4);
-
-
- /** 6x5 Test
- **/
- private static final String START_BOARD_STR_6x5 = ""
- + "xxxxxx|------\n"
- + "--m---|------\n"
- + "------|------\n"
- + "------|------\n"
- + "------|------";
- private static final Point MOVE_TO_6x5 = new Point(10, 4);
- private static final String END_BOARD_STR_6x5 = ""
- + "xxxxxx|------\n"
- + "------|------\n"
- + "------|------\n"
- + "------|------\n"
- + "------|----m-";
- private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
- MOVE_TO_6x5,
- END_BOARD_STR_6x5);
-
- public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
- Map.of(new Point(5, 5), TEST_CASE_5x5,
- new Point(4, 4), TEST_CASE_4x4,
- new Point(6, 5), TEST_CASE_6x5);
-}
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index ee05fe6..91a0634 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -164,7 +164,7 @@
Context c = new ActivityContextWrapper(getApplicationContext());
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(c).getDeviceProfile(c).copy(c);
mPrevNumColumns = dp.inv.numColumns;
- mPrevNumRows = dp.inv.numColumns;
+ mPrevNumRows = dp.inv.numRows;
}
@After
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 243edc3..00d7ce6 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -18,20 +18,24 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.graphics.Point;
+import android.net.Uri;
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.celllayout.testcases.MultipleCellLayoutsSimpleReorder;
+import com.android.launcher3.MultipageCellLayout;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.tapl.WidgetResizeFrame;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.util.rule.ShellCommandRule;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
@@ -44,7 +48,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ExecutionException;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -55,13 +58,14 @@
private static final String TAG = ReorderWidgets.class.getSimpleName();
+ private static final List<String> FOLDABLE_GRIDS = List.of("normal", "practical", "reasonable");
+
TestWorkspaceBuilder mWorkspaceBuilder;
@Before
public void setup() throws Throwable {
- mWorkspaceBuilder = new TestWorkspaceBuilder(this, mTargetContext);
+ mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext);
TaplTestsLauncher3.initialize(this);
- clearHomescreen();
}
/**
@@ -102,20 +106,63 @@
return getFromLauncher(CellLayoutTestUtils::workspaceToBoards);
}
- private void runTestCase(ReorderTestCase testCase)
- throws ExecutionException, InterruptedException {
+ private CellLayoutBoard.WidgetRect getWidgetClosestTo(Point point) {
+ ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards();
+ int maxDistance = 9999;
+ CellLayoutBoard.WidgetRect bestRect = null;
+ for (int i = 0; i < workspaceBoards.get(0).getWidgets().size(); i++) {
+ CellLayoutBoard.WidgetRect widget = workspaceBoards.get(0).getWidgets().get(i);
+ if (widget.getCellX() == 0 && widget.getCellY() == 0) {
+ continue;
+ }
+ int distance = Math.abs(point.x - widget.getCellX())
+ + Math.abs(point.y - widget.getCellY());
+ if (distance == 0) {
+ break;
+ }
+ if (distance < maxDistance) {
+ maxDistance = distance;
+ bestRect = widget;
+ }
+ }
+ return bestRect;
+ }
+
+ /**
+ * This function might be odd, its function is to select a widget and leave it in its place.
+ * The idea is to make the test broader and also test after a widgets resized because the
+ * underlying code does different things in that case
+ */
+ private void triggerWidgetResize(ReorderTestCase testCase) {
+ CellLayoutBoard.WidgetRect widgetRect = getWidgetClosestTo(testCase.moveMainTo);
+ if (widgetRect == null) {
+ // Some test doesn't have a widget in the final position, in those cases we will ignore
+ // them
+ return;
+ }
+ Widget widget = mLauncher.getWorkspace().getWidgetAtCell(widgetRect.getCellX(),
+ widgetRect.getCellY());
+ WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(widgetRect.getCellX(),
+ widgetRect.getCellY(), widgetRect.getSpanX(), widgetRect.getSpanY());
+ resizeFrame.dismiss();
+ }
+
+ private void runTestCase(ReorderTestCase testCase) {
CellLayoutBoard.WidgetRect mainWidgetCellPos = CellLayoutBoard.getMainFromList(
testCase.mStart);
FavoriteItemsTransaction transaction =
- new FavoriteItemsTransaction(mTargetContext, this);
+ new FavoriteItemsTransaction(mTargetContext);
transaction = buildWorkspaceFromBoards(testCase.mStart, transaction);
transaction.commit();
+ mLauncher.waitForLauncherInitialized();
// resetLoaderState triggers the launcher to start loading the workspace which allows
// waitForLauncherCondition to wait for that condition, otherwise the condition would
// always be true and it wouldn't wait for the changes to be applied.
- resetLoaderState();
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
+
+ triggerWidgetResize(testCase);
+
Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(),
mainWidgetCellPos.getCellY());
assertNotNull(widget);
@@ -137,41 +184,91 @@
*
* @param testCaseMap map containing all the tests per grid size (Point)
*/
- private void runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName)
- throws ExecutionException, InterruptedException {
+ private boolean runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName) {
Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions();
Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions);
- Assume.assumeTrue(
- "The test " + testName + " doesn't support " + iconGridDimensions + " grid layout",
- testCaseMap.containsKey(iconGridDimensions));
+ if (!testCaseMap.containsKey(iconGridDimensions)) {
+ Log.d(TAG, "The test " + testName + " doesn't support " + iconGridDimensions
+ + " grid layout");
+ return false;
+ }
runTestCase(testCaseMap.get(iconGridDimensions));
+
+ return true;
+ }
+
+ private void runTestCaseMapForAllGrids(Map<Point, ReorderTestCase> testCaseMap,
+ String testName) {
+ boolean runAtLeastOnce = false;
+ for (String grid : FOLDABLE_GRIDS) {
+ applyGridOption(grid);
+ mLauncher.waitForLauncherInitialized();
+ runAtLeastOnce |= runTestCaseMap(testCaseMap, testName);
+ }
+ Assume.assumeTrue("None of the grids are supported", runAtLeastOnce);
+ }
+
+ private void applyGridOption(Object argValue) {
+ String testProviderAuthority = mTargetContext.getPackageName() + ".grid_control";
+ Uri gridUri = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(testProviderAuthority)
+ .appendPath("default_grid")
+ .build();
+ ContentValues values = new ContentValues();
+ values.putObject("name", argValue);
+ Assert.assertEquals(1,
+ mTargetContext.getContentResolver().update(gridUri, values, null, null));
}
@Test
public void simpleReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/simple_reorder_case"), "push_reorder_case");
+ runTestCaseMap(getTestMap("ReorderWidgets/simple_reorder_case"),
+ "push_reorder_case");
}
@Test
public void pushTest() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/push_reorder_case"), "push_reorder_case");
+ runTestCaseMap(getTestMap("ReorderWidgets/push_reorder_case"),
+ "push_reorder_case");
}
@Test
public void fullReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/full_reorder_case"), "full_reorder_case");
+ runTestCaseMap(getTestMap("ReorderWidgets/full_reorder_case"),
+ "full_reorder_case");
}
@Test
public void moveOutReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/move_out_reorder_case"), "move_out_reorder_case");
+ runTestCaseMap(getTestMap("ReorderWidgets/move_out_reorder_case"),
+ "move_out_reorder_case");
}
@Test
- public void multipleCellLayoutsSimpleReorder() throws ExecutionException, InterruptedException {
- Assume.assumeTrue("Test doesn't support foldables", !mLauncher.isTwoPanels());
- runTestCaseMap(MultipleCellLayoutsSimpleReorder.TEST_BY_GRID_SIZE,
- MultipleCellLayoutsSimpleReorder.class.getSimpleName());
+ public void multipleCellLayoutsSimpleReorder() throws Exception {
+ Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
+ l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
+ runTestCaseMapForAllGrids(getTestMap("ReorderWidgets/multiple_cell_layouts_simple_reorder"),
+ "multiple_cell_layouts_simple_reorder");
+ }
+
+ @Test
+ public void multipleCellLayoutsNoSpaceReorder() throws Exception {
+ Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
+ l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
+ runTestCaseMapForAllGrids(
+ getTestMap("ReorderWidgets/multiple_cell_layouts_no_space_reorder"),
+ "multiple_cell_layouts_no_space_reorder");
+ }
+
+ @Test
+ public void multipleCellLayoutsReorderToOtherSide() throws Exception {
+ Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
+ l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
+ runTestCaseMapForAllGrids(
+ getTestMap("ReorderWidgets/multiple_cell_layouts_reorder_other_side"),
+ "multiple_cell_layouts_reorder_other_side");
}
private void addTestCase(Iterator<CellLayoutTestCaseReader.TestSection> sections,
@@ -184,7 +281,7 @@
((CellLayoutTestCaseReader.Board) sections.next());
Point moveTo = new Point(Integer.parseInt(point.arguments[0]),
Integer.parseInt(point.arguments[1]));
- testCaseMap.put(startBoard.gridSize,
+ testCaseMap.put(endBoard.gridSize,
new ReorderTestCase(startBoard.board, moveTo, endBoard.board));
}
diff --git a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
index 5945605..6489bc1 100644
--- a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
+++ b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
@@ -15,6 +15,10 @@
*/
package com.android.launcher3.celllayout;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.ui.TestViewHelpers.findWidgetProvider;
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
import android.content.ComponentName;
@@ -25,33 +29,30 @@
import android.os.UserHandle;
import android.util.Log;
-import androidx.test.core.app.ApplicationProvider;
-
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+
public class TestWorkspaceBuilder {
private static final String TAG = "CellLayoutBoardBuilder";
- private static final ComponentName APP_COMPONENT_NAME = new ComponentName(
+ private static final String TEST_ACTIVITY_PACKAGE_PREFIX = "com.android.launcher3.tests.";
+ private ComponentName mAppComponentName = new ComponentName(
"com.google.android.calculator", "com.android.calculator2.Calculator");
-
- public AbstractLauncherUiTest mTest;
-
private UserHandle mMyUser;
private Context mContext;
private ContentResolver mResolver;
- public TestWorkspaceBuilder(AbstractLauncherUiTest test, Context context) {
- mTest = test;
+ public TestWorkspaceBuilder(Context context) {
mMyUser = Process.myUserHandle();
mContext = context;
mResolver = mContext.getContentResolver();
@@ -68,10 +69,9 @@
for (int y = initY; y < initY + widgetRect.getSpanY(); y++) {
try {
// this widgets are filling, we don't care if we can't place them
- ItemInfo item = createWidgetInCell(
+ transaction.addItem(createWidgetInCell(
new CellLayoutBoard.WidgetRect(CellLayoutBoard.CellType.IGNORE,
- new Rect(x, y, x, y)), screenId);
- transaction.addItem(item);
+ new Rect(x, y, x, y)), screenId));
} catch (Exception e) {
Log.d(TAG, "Unable to place filling widget at " + x + "," + y);
}
@@ -80,15 +80,21 @@
return transaction;
}
- private int getID() {
- return LauncherSettings.Settings.call(
- mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ private AppInfo getApp() {
+ return new AppInfo(mAppComponentName, "test icon", mMyUser,
+ AppInfo.makeLaunchIntent(mAppComponentName));
}
- private AppInfo getApp() {
- return new AppInfo(APP_COMPONENT_NAME, "test icon", mMyUser,
- AppInfo.makeLaunchIntent(APP_COMPONENT_NAME));
+ /**
+ * Helper to set the app to use for the test workspace,
+ * using activity-alias from AndroidManifest-common.
+ * @param testAppName the android:name field of the test app activity-alias to use
+ */
+ public void setTestAppActivityAlias(String testAppName) {
+ this.mAppComponentName = new ComponentName(
+ getInstrumentation().getContext().getPackageName(),
+ TEST_ACTIVITY_PACKAGE_PREFIX + testAppName
+ );
}
private void addCorrespondingWidgetRect(CellLayoutBoard.WidgetRect widgetRect,
@@ -108,7 +114,10 @@
board.getWidgets().forEach(
(widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction, screenId));
board.getIcons().forEach((iconPoint) ->
- transaction.addItem(createIconInCell(iconPoint, screenId))
+ transaction.addItem(() -> createIconInCell(iconPoint, screenId))
+ );
+ board.getFolders().forEach((folderPoint) ->
+ transaction.addItem(() -> createFolderInCell(folderPoint, screenId))
);
return transaction;
}
@@ -118,29 +127,52 @@
* be clean otherwise this doesn't overrides the existing icons.
*/
public FavoriteItemsTransaction fillHotseatIcons(FavoriteItemsTransaction transaction) {
- int hotseatCount = InvariantDeviceProfile.INSTANCE.get(mContext).numDatabaseHotseatIcons;
- for (int i = 0; i < hotseatCount; i++) {
- transaction.addItem(getHotseatValues(i));
- }
+ IntStream.range(0, InvariantDeviceProfile.INSTANCE.get(mContext).numDatabaseHotseatIcons)
+ .forEach(i -> transaction.addItem(() -> getHotseatValues(i)));
return transaction;
}
- private ItemInfo createWidgetInCell(CellLayoutBoard.WidgetRect widgetRect, int screenId) {
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(mTest, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info,
- ApplicationProvider.getApplicationContext(), true);
- item.id = getID();
- item.cellX = widgetRect.getCellX();
- item.cellY = widgetRect.getCellY();
- item.spanX = widgetRect.getSpanX();
- item.spanY = widgetRect.getSpanY();
+ private Supplier<ItemInfo> createWidgetInCell(
+ CellLayoutBoard.WidgetRect widgetRect, int screenId) {
+ // Create the widget lazily since the appWidgetId can get lost during setup
+ return () -> {
+ LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
+ LauncherAppWidgetInfo item = createWidgetInfo(info, getApplicationContext(), true);
+ item.cellX = widgetRect.getCellX();
+ item.cellY = widgetRect.getCellY();
+ item.spanX = widgetRect.getSpanX();
+ item.spanY = widgetRect.getSpanY();
+ item.screenId = screenId;
+ return item;
+ };
+ }
+
+ public FolderInfo createFolderInCell(CellLayoutBoard.FolderPoint folderPoint, int screenId) {
+ FolderInfo folderInfo = new FolderInfo();
+ folderInfo.screenId = screenId;
+ folderInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ folderInfo.cellX = folderPoint.coord.x;
+ folderInfo.cellY = folderPoint.coord.y;
+ folderInfo.minSpanY = folderInfo.minSpanX = folderInfo.spanX = folderInfo.spanY = 1;
+ folderInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null);
+
+ for (int i = 0; i < folderPoint.getNumberIconsInside(); i++) {
+ folderInfo.add(getDefaultWorkspaceItem(screenId), false);
+ }
+
+ return folderInfo;
+ }
+
+ private WorkspaceItemInfo getDefaultWorkspaceItem(int screenId) {
+ WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
item.screenId = screenId;
+ item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
+ item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
return item;
}
private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint, int screenId) {
WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
- item.id = getID();
item.screenId = screenId;
item.cellX = iconPoint.getCoord().x;
item.cellY = iconPoint.getCoord().y;
@@ -151,7 +183,6 @@
private ItemInfo getHotseatValues(int x) {
WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
- item.id = getID();
item.cellX = x;
item.cellY = 0;
item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
diff --git a/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java b/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
new file mode 100644
index 0000000..715a1f8
--- /dev/null
+++ b/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
@@ -0,0 +1,323 @@
+/*
+ * 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.folder;
+
+import static com.android.launcher3.folder.PreviewBackground.ACCEPT_SCALE_FACTOR;
+import static com.android.launcher3.folder.PreviewBackground.CONSUMPTION_ANIMATION_DURATION;
+import static com.android.launcher3.folder.PreviewBackground.HOVER_ANIMATION_DURATION;
+import static com.android.launcher3.folder.PreviewBackground.HOVER_SCALE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.PathInterpolator;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.CellLayout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class PreviewBackgroundTest {
+
+ private static final float REST_SCALE = 1f;
+ private static final float EPSILON = 0.00001f;
+
+ @Mock
+ CellLayout mCellLayout;
+
+ private final PreviewBackground mPreviewBackground = new PreviewBackground();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mPreviewBackground.mScale = REST_SCALE;
+ mPreviewBackground.mIsAccepting = false;
+ mPreviewBackground.mIsHovered = false;
+ mPreviewBackground.mIsHoveredOrAnimating = false;
+ mPreviewBackground.invalidate();
+ }
+
+ @Test
+ public void testAnimateScale_restToHovered() {
+ mPreviewBackground.setHovered(true);
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ HOVER_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_restToNotHovered() {
+ mPreviewBackground.setHovered(false);
+
+ assertEquals("Scale changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON);
+ assertNull("Animator not null.", mPreviewBackground.mScaleAnimator);
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_hoveredToHovered() {
+ mPreviewBackground.mScale = HOVER_SCALE;
+ mPreviewBackground.mIsHovered = true;
+ mPreviewBackground.mIsHoveredOrAnimating = true;
+ mPreviewBackground.invalidate();
+
+ mPreviewBackground.setHovered(true);
+
+ assertEquals("Scale changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON);
+ assertNull("Animator not null.", mPreviewBackground.mScaleAnimator);
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_hoveredToRest() {
+ mPreviewBackground.mScale = HOVER_SCALE;
+ mPreviewBackground.mIsHovered = true;
+ mPreviewBackground.mIsHoveredOrAnimating = true;
+ mPreviewBackground.invalidate();
+
+ mPreviewBackground.setHovered(false);
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ HOVER_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_restToAccept() {
+ mPreviewBackground.animateToAccept(mCellLayout, 0, 0);
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale changed.", mPreviewBackground.mScale, ACCEPT_SCALE_FACTOR, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ CONSUMPTION_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator()
+ instanceof AccelerateDecelerateInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 1.", mPreviewBackground.getAcceptScaleProgress(), 1,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_restToRest() {
+ mPreviewBackground.animateToRest();
+
+ assertEquals("Scale changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON);
+ assertNull("Animator not null.", mPreviewBackground.mScaleAnimator);
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_acceptToRest() {
+ mPreviewBackground.mScale = ACCEPT_SCALE_FACTOR;
+ mPreviewBackground.mIsAccepting = true;
+ mPreviewBackground.invalidate();
+
+ mPreviewBackground.animateToRest();
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ CONSUMPTION_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator()
+ instanceof AccelerateDecelerateInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_acceptToHover() {
+ mPreviewBackground.mScale = ACCEPT_SCALE_FACTOR;
+ mPreviewBackground.mIsAccepting = true;
+ mPreviewBackground.invalidate();
+
+ mPreviewBackground.mIsAccepting = false;
+ mPreviewBackground.setHovered(true);
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ HOVER_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_hoverToAccept() {
+ mPreviewBackground.mScale = HOVER_SCALE;
+ mPreviewBackground.mIsHovered = true;
+ mPreviewBackground.mIsHoveredOrAnimating = true;
+ mPreviewBackground.invalidate();
+
+ mPreviewBackground.animateToAccept(mCellLayout, 0, 0);
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, ACCEPT_SCALE_FACTOR, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ CONSUMPTION_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator()
+ instanceof AccelerateDecelerateInterpolator);
+ mPreviewBackground.mIsHovered = false;
+ endAnimation();
+ assertEquals("Scale progress not 1.", mPreviewBackground.getAcceptScaleProgress(), 1,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_midwayToHoverToAccept() {
+ mPreviewBackground.setHovered(true);
+ runAnimationToFraction(0.5f);
+ assertTrue("Scale not changed.",
+ mPreviewBackground.mScale > REST_SCALE && mPreviewBackground.mScale < HOVER_SCALE);
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+
+ mPreviewBackground.animateToAccept(mCellLayout, 0, 0);
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, ACCEPT_SCALE_FACTOR, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ CONSUMPTION_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator()
+ instanceof AccelerateDecelerateInterpolator);
+ mPreviewBackground.mIsHovered = false;
+ endAnimation();
+ assertEquals("Scale progress not 1.", mPreviewBackground.getAcceptScaleProgress(), 1,
+ EPSILON);
+ assertNull("Animator not null.", mPreviewBackground.mScaleAnimator);
+ }
+
+ @Test
+ public void testAnimateScale_partWayToAcceptToHover() {
+ mPreviewBackground.animateToAccept(mCellLayout, 0, 0);
+ runAnimationToFraction(0.25f);
+ assertTrue("Scale not changed part way.", mPreviewBackground.mScale > REST_SCALE
+ && mPreviewBackground.mScale < ACCEPT_SCALE_FACTOR);
+
+ mPreviewBackground.mIsAccepting = false;
+ mPreviewBackground.setHovered(true);
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ HOVER_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_midwayToAcceptEqualsHover() {
+ mPreviewBackground.animateToAccept(mCellLayout, 0, 0);
+ runAnimationToFraction(0.5f);
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON);
+ mPreviewBackground.mIsAccepting = false;
+
+ mPreviewBackground.setHovered(true);
+
+ assertEquals("Scale changed.", mPreviewBackground.mScale, HOVER_SCALE, EPSILON);
+ assertNull("Animator not null.", mPreviewBackground.mScaleAnimator);
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_midwayToHoverToRest() {
+ mPreviewBackground.setHovered(true);
+ runAnimationToFraction(0.5f);
+ assertTrue("Scale not changed midway.",
+ mPreviewBackground.mScale > REST_SCALE && mPreviewBackground.mScale < HOVER_SCALE);
+
+ mPreviewBackground.mIsHovered = false;
+ mPreviewBackground.animateToRest();
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ HOVER_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator() instanceof PathInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ @Test
+ public void testAnimateScale_midwayToAcceptToRest() {
+ mPreviewBackground.animateToAccept(mCellLayout, 0, 0);
+ runAnimationToFraction(0.5f);
+ assertTrue("Scale not changed.", mPreviewBackground.mScale > REST_SCALE
+ && mPreviewBackground.mScale < ACCEPT_SCALE_FACTOR);
+
+ mPreviewBackground.animateToRest();
+ runAnimationToFraction(1f);
+
+ assertEquals("Scale not changed.", mPreviewBackground.mScale, REST_SCALE, EPSILON);
+ assertEquals("Duration not correct.", mPreviewBackground.mScaleAnimator.getDuration(),
+ CONSUMPTION_ANIMATION_DURATION);
+ assertTrue("Wrong interpolator used.",
+ mPreviewBackground.mScaleAnimator.getInterpolator()
+ instanceof AccelerateDecelerateInterpolator);
+ endAnimation();
+ assertEquals("Scale progress not 0.", mPreviewBackground.getAcceptScaleProgress(), 0,
+ EPSILON);
+ }
+
+ private void runAnimationToFraction(float animationFraction) {
+ mPreviewBackground.mScaleAnimator.setCurrentFraction(animationFraction);
+ }
+
+ private void endAnimation() {
+ mPreviewBackground.mScaleAnimator.end();
+ }
+}
diff --git a/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java b/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
new file mode 100644
index 0000000..038c98b
--- /dev/null
+++ b/tests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
@@ -0,0 +1,329 @@
+/*
+ * 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.icons;
+
+import static com.android.launcher3.icons.FastBitmapDrawable.CLICK_FEEDBACK_DURATION;
+import static com.android.launcher3.icons.FastBitmapDrawable.HOVERED_SCALE;
+import static com.android.launcher3.icons.FastBitmapDrawable.HOVER_FEEDBACK_DURATION;
+import static com.android.launcher3.icons.FastBitmapDrawable.PRESSED_SCALE;
+import static com.android.launcher3.icons.FastBitmapDrawable.SCALE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.PathInterpolator;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+
+/**
+ * Tests for FastBitmapDrawable.
+ */
+@SmallTest
+@UiThreadTest
+@RunWith(AndroidJUnit4.class)
+public class FastBitmapDrawableTest {
+ private static final float EPSILON = 0.00001f;
+
+ @Spy
+ FastBitmapDrawable mFastBitmapDrawable =
+ spy(new FastBitmapDrawable(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)));
+
+ @Before
+ public void setUp() {
+ FastBitmapDrawable.setFlagHoverEnabled(true);
+ when(mFastBitmapDrawable.isVisible()).thenReturn(true);
+ mFastBitmapDrawable.mIsPressed = false;
+ mFastBitmapDrawable.mIsHovered = false;
+ mFastBitmapDrawable.resetScale();
+ }
+
+ @Test
+ public void testOnStateChange_noState() {
+ int[] state = new int[]{};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No scale changes without state change.
+ assertFalse("State change handled.", isHandled);
+ assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation);
+ }
+
+ @Test
+ public void testOnStateChange_statePressed() {
+ int[] state = new int[]{android.R.attr.state_pressed};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to state pressed.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
+ CLICK_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator()
+ instanceof AccelerateInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_stateHovered() {
+ int[] state = new int[]{android.R.attr.state_hovered};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to state hovered.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
+ HOVER_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_stateHoveredFlagDisabled() {
+ FastBitmapDrawable.setFlagHoverEnabled(false);
+ int[] state = new int[]{android.R.attr.state_hovered};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No state change with flag disabled.
+ assertFalse("Hover state change handled with flag disabled.", isHandled);
+ assertNull("Animation should not run with hover flag disabled.",
+ mFastBitmapDrawable.mScaleAnimation);
+ }
+
+ @Test
+ public void testOnStateChange_statePressedAndHovered() {
+ int[] state = new int[]{android.R.attr.state_pressed, android.R.attr.state_hovered};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to pressed state only.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
+ CLICK_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator()
+ instanceof AccelerateInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_stateHoveredAndPressed() {
+ int[] state = new int[]{android.R.attr.state_hovered, android.R.attr.state_pressed};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to pressed state only.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
+ CLICK_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator()
+ instanceof AccelerateInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_stateHoveredAndPressedToPressed() {
+ mFastBitmapDrawable.mIsPressed = true;
+ mFastBitmapDrawable.mIsHovered = true;
+ SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
+ int[] state = new int[]{android.R.attr.state_pressed};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No scale change from pressed state to pressed state.
+ assertTrue("State not changed.", isHandled);
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
+ }
+
+ @Test
+ public void testOnStateChange_stateHoveredAndPressedToHovered() {
+ mFastBitmapDrawable.mIsPressed = true;
+ mFastBitmapDrawable.mIsHovered = true;
+ SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
+ int[] state = new int[]{android.R.attr.state_hovered};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No scale change from pressed state to hovered state.
+ assertTrue("State not changed.", isHandled);
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
+ }
+
+ @Test
+ public void testOnStateChange_stateHoveredToPressed() {
+ mFastBitmapDrawable.mIsHovered = true;
+ SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE);
+ int[] state = new int[]{android.R.attr.state_pressed};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No scale change from pressed state to hovered state.
+ assertTrue("State not changed.", isHandled);
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
+ }
+
+ @Test
+ public void testOnStateChange_statePressedToHovered() {
+ mFastBitmapDrawable.mIsPressed = true;
+ SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
+ int[] state = new int[]{android.R.attr.state_hovered};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No scale change from pressed state to hovered state.
+ assertTrue("State not changed.", isHandled);
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
+ }
+
+ @Test
+ public void testOnStateChange_stateDefaultFromPressed() {
+ mFastBitmapDrawable.mIsPressed = true;
+ SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
+ int[] state = new int[]{};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to default state from pressed state.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
+ CLICK_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator()
+ instanceof DecelerateInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_stateDefaultFromHovered() {
+ mFastBitmapDrawable.mIsHovered = true;
+ SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE);
+ int[] state = new int[]{};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to default state from hovered state.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(),
+ HOVER_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_stateHoveredWhilePartiallyScaled() {
+ SCALE.setValue(mFastBitmapDrawable, 0.5f);
+ int[] state = new int[]{android.R.attr.state_hovered};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to hovered state from midway to pressed state.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.",
+ mFastBitmapDrawable.mScaleAnimation.getDuration(), HOVER_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_statePressedWhilePartiallyScaled() {
+ SCALE.setValue(mFastBitmapDrawable, 0.5f);
+ int[] state = new int[]{android.R.attr.state_pressed};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // Animate to pressed state from midway to hovered state.
+ assertTrue("State change not handled.", isHandled);
+ assertEquals("Duration not correct.",
+ mFastBitmapDrawable.mScaleAnimation.getDuration(), CLICK_FEEDBACK_DURATION);
+ mFastBitmapDrawable.mScaleAnimation.end();
+ assertEquals("End value not correct.",
+ (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON);
+ assertTrue("Wrong interpolator used.",
+ mFastBitmapDrawable.mScaleAnimation.getInterpolator()
+ instanceof AccelerateInterpolator);
+ }
+
+ @Test
+ public void testOnStateChange_stateDefaultFromPressedNotVisible() {
+ when(mFastBitmapDrawable.isVisible()).thenReturn(false);
+ mFastBitmapDrawable.mIsPressed = true;
+ SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE);
+ clearInvocations(mFastBitmapDrawable);
+ int[] state = new int[]{};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No animations when state was pressed but drawable no longer visible. Set values directly.
+ assertTrue("State change not handled.", isHandled);
+ assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation);
+ assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
+ verify(mFastBitmapDrawable).invalidateSelf();
+ }
+
+ @Test
+ public void testOnStateChange_stateDefaultFromHoveredNotVisible() {
+ when(mFastBitmapDrawable.isVisible()).thenReturn(false);
+ mFastBitmapDrawable.mIsHovered = true;
+ SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE);
+ clearInvocations(mFastBitmapDrawable);
+ int[] state = new int[]{};
+
+ boolean isHandled = mFastBitmapDrawable.onStateChange(state);
+
+ // No animations when state was hovered but drawable no longer visible. Set values directly.
+ assertTrue("State change not handled.", isHandled);
+ assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation);
+ assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON);
+ verify(mFastBitmapDrawable).invalidateSelf();
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt b/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt
index 03352fe..98191fe 100644
--- a/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt
+++ b/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt
@@ -17,17 +17,18 @@
import android.content.ComponentName
import android.content.Context
-import android.content.Intent
import android.graphics.Rect
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
-import com.android.launcher3.LauncherSettings
+import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.util.ContentWriter
import com.android.launcher3.util.GridOccupancy
import com.android.launcher3.util.IntArray
import com.android.launcher3.util.IntSparseArrayMap
+import com.android.launcher3.util.LauncherLayoutBuilder
import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY
+import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
import java.util.UUID
/** Base class for workspace related tests. */
@@ -38,6 +39,7 @@
val nonEmptyScreenSpaces = listOf(Rect(1, 2, 3, 4))
}
+ protected lateinit var mLayoutBuilder: LauncherLayoutBuilder
protected lateinit var mTargetContext: Context
protected lateinit var mIdp: InvariantDeviceProfile
protected lateinit var mAppState: LauncherAppState
@@ -47,6 +49,7 @@
protected lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
open fun setup() {
+ mLayoutBuilder = LauncherLayoutBuilder()
mModelHelper = LauncherModelHelper()
mTargetContext = mModelHelper.sandboxContext
mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
@@ -64,10 +67,11 @@
/** Sets up workspaces with the given screen IDs with some items and a 2x2 space. */
fun setupWorkspaces(screenIdsWithItems: List<Int>) {
- var nextItemId = 1
- screenIdsWithItems.forEach { screenId ->
- nextItemId = setupWorkspace(nextItemId, screenId, nonEmptyScreenSpaces)
- }
+ screenIdsWithItems.forEach { screenId -> setupWorkspace(screenId, nonEmptyScreenSpaces) }
+ mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder)
+ mIdp.numRows = 5
+ mIdp.numColumns = mIdp.numRows
+ mModelHelper.loadModelSync()
}
/**
@@ -78,30 +82,23 @@
screen1: List<Rect>? = null,
screen2: List<Rect>? = null,
screen3: List<Rect>? = null,
- ) = listOf(screen0, screen1, screen2, screen3).let(this::setupWithSpaces)
+ ) {
+ listOf(screen0, screen1, screen2, screen3).let(this::setupWithSpaces)
+ mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder)
+ mIdp.numRows = 5
+ mIdp.numColumns = mIdp.numRows
+ mModelHelper.loadModelSync()
+ }
private fun setupWithSpaces(workspaceSpaces: List<List<Rect>?>) {
- var nextItemId = 1
workspaceSpaces.forEachIndexed { screenId, spaces ->
if (spaces != null) {
- nextItemId = setupWorkspace(nextItemId, screenId, spaces)
+ setupWorkspace(screenId, spaces)
}
}
}
- private fun setupWorkspace(startId: Int, screenId: Int, spaces: List<Rect>): Int {
- return mModelHelper.executeSimpleTask { dataModel ->
- writeWorkspaceWithSpaces(dataModel, startId, screenId, spaces)
- }
- }
-
- private fun writeWorkspaceWithSpaces(
- bgDataModel: BgDataModel,
- itemStartId: Int,
- screenId: Int,
- spaces: List<Rect>,
- ): Int {
- var itemId = itemStartId
+ private fun setupWorkspace(screenId: Int, spaces: List<Rect>) {
val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
spaces.forEach { spaceRect -> occupancy.markCells(spaceRect, false) }
@@ -109,35 +106,22 @@
mScreenOccupancy.append(screenId, occupancy)
for (x in 0 until mIdp.numColumns) {
for (y in 0 until mIdp.numRows) {
- if (!occupancy.cells[x][y]) {
- continue
+ if (occupancy.cells[x][y]) {
+ mLayoutBuilder.atWorkspace(x, y, screenId).putApp(TEST_PACKAGE, TEST_ACTIVITY)
}
- val info = getExistingItem()
- info.id = itemId++
- info.screenId = screenId
- info.cellX = x
- info.cellY = y
- info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
- bgDataModel.addItem(mTargetContext, info, false)
- val writer = ContentWriter(mTargetContext)
- info.writeToValues(writer)
- writer.put(LauncherSettings.Favorites._ID, info.id)
- mTargetContext.contentResolver.insert(
- LauncherSettings.Favorites.CONTENT_URI,
- writer.getValues(mTargetContext)
- )
}
}
- return itemId
}
fun getExistingItem() =
- WorkspaceItemInfo().apply { intent = Intent().setComponent(ComponentName("a", "b")) }
+ WorkspaceItemInfo().apply {
+ intent = AppInfo.makeLaunchIntent(ComponentName(TEST_PACKAGE, TEST_ACTIVITY))
+ }
fun getNewItem(): WorkspaceItemInfo {
val itemPackage = UUID.randomUUID().toString()
return WorkspaceItemInfo().apply {
- intent = Intent().setComponent(ComponentName(itemPackage, itemPackage))
+ intent = AppInfo.makeLaunchIntent(ComponentName(itemPackage, itemPackage))
}
}
}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 6636b8a..1155227 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -23,7 +23,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.Executors
import com.android.launcher3.util.IntArray
-import com.android.launcher3.util.IntSet
+import com.android.launcher3.util.TestUtil.runOnExecutorSync
import com.android.launcher3.util.any
import com.android.launcher3.util.eq
import com.android.launcher3.util.same
@@ -32,8 +32,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -46,11 +44,7 @@
@RunWith(AndroidJUnit4::class)
class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
- @Captor private lateinit var mAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
-
- @Captor private lateinit var mNotAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
-
- @Mock private lateinit var mDataModelCallbacks: BgDataModel.Callbacks
+ private lateinit var mDataModelCallbacks: MyCallbacks
@Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
@@ -58,7 +52,7 @@
override fun setup() {
super.setup()
MockitoAnnotations.initMocks(this)
- whenever(mDataModelCallbacks.getPagesToBindSynchronously(any())).thenReturn(IntSet())
+ mDataModelCallbacks = MyCallbacks()
Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
.get()
}
@@ -105,7 +99,7 @@
val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
assertThat(addedItems.size).isEqualTo(0)
- verifyZeroInteractions(mWorkspaceItemSpaceFinder, mDataModelCallbacks)
+ verifyZeroInteractions(mWorkspaceItemSpaceFinder)
}
@Test
@@ -191,22 +185,14 @@
): List<AddedItem> {
setupWorkspaces(nonEmptyScreenIds)
val task = newTask(*itemsToAdd)
- var updateCount = 0
- mModelHelper.executeTaskForTest(task).forEach {
- updateCount++
- it.run()
- }
val addedItems = mutableListOf<AddedItem>()
- if (updateCount > 0) {
- verify(mDataModelCallbacks)
- .bindAppsAdded(
- any(),
- mNotAnimatedItemArgumentCaptor.capture(),
- mAnimatedItemArgumentCaptor.capture()
- )
- addedItems.addAll(mAnimatedItemArgumentCaptor.value.map { AddedItem(it, true) })
- addedItems.addAll(mNotAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
+
+ runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ mDataModelCallbacks.addedItems.clear()
+ mModelHelper.model.enqueueModelUpdateTask(task)
+ runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
+ addedItems.addAll(mDataModelCallbacks.addedItems)
}
return addedItems
@@ -224,3 +210,17 @@
}
private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean)
+
+private class MyCallbacks : BgDataModel.Callbacks {
+
+ val addedItems = mutableListOf<AddedItem>()
+
+ override fun bindAppsAdded(
+ newScreens: IntArray?,
+ addNotAnimated: ArrayList<ItemInfo>,
+ addAnimated: ArrayList<ItemInfo>
+ ) {
+ addedItems.addAll(addAnimated.map { AddedItem(it, true) })
+ addedItems.addAll(addNotAnimated.map { AddedItem(it, false) })
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index f55b244..f771052 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -1,32 +1,31 @@
package com.android.launcher3.model;
+import static android.os.Process.myUserHandle;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Color;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.PackageUserKey;
import org.junit.After;
import org.junit.Before;
@@ -35,6 +34,7 @@
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
/**
* Tests for {@link CacheDataUpdatedTask}
@@ -43,49 +43,40 @@
@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest {
- private static final String NEW_LABEL_PREFIX = "new-label-";
+ private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
+ private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
private LauncherModelHelper mModelHelper;
+ private Context mContext;
+
+ private int mSession1;
@Before
public void setup() throws Exception {
mModelHelper = new LauncherModelHelper();
- mModelHelper.initializeData("cache_data_updated_task_data");
+ mContext = mModelHelper.sandboxContext;
+ mSession1 = mModelHelper.createInstallerSession(PENDING_APP_1);
+ mModelHelper.createInstallerSession(PENDING_APP_2);
- // Add placeholder entries in the cache to simulate update
- Context context = mModelHelper.sandboxContext;
- IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
- CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() {
- @Override
- @NonNull
- public ComponentName getComponent(@NonNull ItemInfo info) {
- return info.getTargetComponent();
- }
+ LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+ .atHotseat(1).putFolder("MyFolder")
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY) // 2
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY2) // 3
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY3) // 4
- @NonNull
- @Override
- public UserHandle getUser(@NonNull ItemInfo info) {
- return info.user;
- }
+ // Pending App 1
+ .addApp(PENDING_APP_1, TEST_ACTIVITY) // 5
+ .addApp(PENDING_APP_1, TEST_ACTIVITY2) // 6
+ .addApp(PENDING_APP_1, TEST_ACTIVITY3) // 7
- @NonNull
- @Override
- public CharSequence getLabel(@NonNull ItemInfo info) {
- return NEW_LABEL_PREFIX + info.id;
- }
-
- @NonNull
- @Override
- public BitmapInfo loadIcon(@NonNull Context context, @NonNull ItemInfo info) {
- return BitmapInfo.of(Bitmap.createBitmap(1, 1, Config.ARGB_8888), Color.RED);
- }
- };
-
- UserManager um = context.getSystemService(UserManager.class);
- for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
- iconCache.addIconToDBAndMemCache(info, placeholderLogic, new PackageInfo(),
- um.getSerialNumberForUser(info.user), true);
- }
+ // Pending App 2
+ .addApp(PENDING_APP_2, TEST_ACTIVITY) // 8
+ .addApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
+ .addApp(PENDING_APP_2, TEST_ACTIVITY3) // 10
+ .build();
+ mModelHelper.setupDefaultLayoutProvider(builder);
+ mModelHelper.loadModelSync();
+ assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
}
@After
@@ -94,27 +85,63 @@
}
private CacheDataUpdatedTask newTask(int op, String... pkg) {
- return new CacheDataUpdatedTask(op, Process.myUserHandle(),
+ return new CacheDataUpdatedTask(op, myUserHandle(),
new HashSet<>(Arrays.asList(pkg)));
}
@Test
- public void testCacheUpdate_update_apps() throws Exception {
- // Clear all icons from apps list so that its easy to check what was updated
- for (AppInfo info : mModelHelper.getAllAppsList().data) {
- info.bitmap = BitmapInfo.LOW_RES_INFO;
- }
+ public void testCacheUpdate_update_apps() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ // Clear all icons from apps list so that its easy to check what was updated
+ allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
- mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, TEST_PACKAGE));
- // Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
- // is not updated
- verifyUpdate(1, 2);
+ // Verify that only the app icons of TEST_PACKAGE (id 2, 3, 4) are updated.
+ verifyUpdate(2, 3, 4);
+ });
+ }
- // Verify that only app1 var updated in allAppsList
- assertFalse(mModelHelper.getAllAppsList().data.isEmpty());
- for (AppInfo info : mModelHelper.getAllAppsList().data) {
- if (info.componentName.getPackageName().equals("app1")) {
+ @Test
+ public void testSessionUpdate_ignores_normal_apps() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ // Clear all icons from apps list so that its easy to check what was updated
+ allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
+
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, TEST_PACKAGE));
+
+ // TEST_PACKAGE has no restored shortcuts. Verify that nothing was updated.
+ verifyUpdate();
+ });
+ }
+
+ @Test
+ public void testSessionUpdate_updates_pending_apps() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ LauncherAppState.getInstance(mContext).getIconCache().updateSessionCache(
+ new PackageUserKey(PENDING_APP_1, myUserHandle()),
+ mContext.getPackageManager().getPackageInstaller().getSessionInfo(mSession1));
+
+ // Clear all icons from apps list so that its easy to check what was updated
+ allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
+
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, PENDING_APP_1));
+
+ // Only restored apps from PENDING_APP_1 (id 5, 6, 7) are updated
+ verifyUpdate(5, 6, 7);
+ });
+ }
+
+ private void verifyUpdate(int... idsUpdated) {
+ IntSet updates = IntSet.wrap(idsUpdated);
+ for (WorkspaceItemInfo info : allItems()) {
+ if (updates.contains(info.id)) {
assertFalse(info.bitmap.isNullOrLowRes());
} else {
assertTrue(info.bitmap.isNullOrLowRes());
@@ -122,33 +149,7 @@
}
}
- @Test
- public void testSessionUpdate_ignores_normal_apps() throws Exception {
- mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
-
- // app1 has no restored shortcuts. Verify that nothing was updated.
- verifyUpdate();
- }
-
- @Test
- public void testSessionUpdate_updates_pending_apps() throws Exception {
- mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
-
- // app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
- // were updated
- verifyUpdate(5, 6);
- }
-
- private void verifyUpdate(Integer... idsUpdated) {
- HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
- for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
- if (updates.contains(info.id)) {
- assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
- assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
- } else {
- assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
- assertTrue(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
- }
- }
+ private List<WorkspaceItemInfo> allItems() {
+ return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).contents;
}
}
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index 3b480ca..4fa5352 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -24,7 +24,6 @@
import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
@@ -108,8 +107,8 @@
fun testMigration() {
// Src Hotseat icons
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
- addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
- addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
// Src grid icons
// _ _ _ _ _
@@ -124,7 +123,7 @@
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE)
// Dest hotseat icons
- addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
// Dest grid icons
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10)
@@ -219,8 +218,8 @@
// Hotseat items in grid A
// 1 2 _ 3 4
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
- addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
- addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
// Workspace items in grid A
// _ _ _ _ _
@@ -235,7 +234,7 @@
// Hotseat items in grid B
// 2 _ _ _
- addItem(ITEM_TYPE_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
// Workspace items in grid B
// _ _ _ _
// _ _ _ 10
@@ -291,7 +290,7 @@
null
)
?: throw IllegalStateException()
- var locMap = parseLocMap(context, c)
+ var locMap = parseLocMap(c)
// Expected items in grid B
// _ _ _ _
// 5 6 7 8
@@ -348,7 +347,7 @@
null
)
?: throw IllegalStateException()
- locMap = parseLocMap(context, c)
+ locMap = parseLocMap(c)
// Expected workspace items in grid A
// _ _ _ _ _
// _ _ _ _ 5
@@ -410,7 +409,7 @@
null
)
?: throw IllegalStateException()
- locMap = parseLocMap(context, c)
+ locMap = parseLocMap(c)
// Expected workspace items in grid B
// _ _ _ _
// 5 6 _ 8
@@ -436,7 +435,7 @@
c.close()
}
- private fun parseLocMap(context: Context, c: Cursor): Map<String, Triple<Int, Int, Int>> {
+ private fun parseLocMap(c: Cursor): Map<String, Triple<Int, Int, Int>> {
// Check workspace items
val intentIndex = c.getColumnIndex(INTENT)
val screenIndex = c.getColumnIndex(SCREEN)
@@ -465,7 +464,16 @@
1,
TMP_TABLE
),
- addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE),
+ addItem(
+ ITEM_TYPE_DEEP_SHORTCUT,
+ 1,
+ CONTAINER_HOTSEAT,
+ 0,
+ 0,
+ testPackage2,
+ 2,
+ TMP_TABLE
+ ),
addItem(
ITEM_TYPE_APPLICATION,
2,
@@ -476,7 +484,16 @@
3,
TMP_TABLE
),
- addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
+ addItem(
+ ITEM_TYPE_DEEP_SHORTCUT,
+ 3,
+ CONTAINER_HOTSEAT,
+ 0,
+ 0,
+ testPackage4,
+ 4,
+ TMP_TABLE
+ )
)
val numSrcDatabaseHotseatIcons = srcHotseatItems.size
idp.numDatabaseHotseatIcons = 6
@@ -532,9 +549,9 @@
@Test
fun migrateFromLargerHotseat() {
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
- addItem(ITEM_TYPE_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
- addItem(ITEM_TYPE_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
addItem(ITEM_TYPE_APPLICATION, 5, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
idp.numDatabaseHotseatIcons = 4
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 78812c0..544ed6b 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -30,7 +30,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.OPTIONS;
import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
import static com.android.launcher3.LauncherSettings.Favorites.RANK;
@@ -158,13 +158,13 @@
@Test
public void loadSimpleShortcut() {
- initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut");
+ initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut");
assertTrue(mLoaderCursor.moveToNext());
WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user));
assertEquals("my-shortcut", info.title);
- assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
+ assertEquals(ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
}
@Test
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index 519191e..4ba61ac 100644
--- a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -1,5 +1,12 @@
package com.android.launcher3.model;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
+
import static org.junit.Assert.assertEquals;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -9,6 +16,8 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import org.junit.After;
@@ -16,9 +25,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
-import java.util.HashSet;
-
/**
* Tests for {@link PackageInstallStateChangedTask}
*/
@@ -26,12 +32,36 @@
@RunWith(AndroidJUnit4.class)
public class PackageInstallStateChangedTaskTest {
+ private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
+ private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
+
private LauncherModelHelper mModelHelper;
+ private IntSet mDownloadingApps;
@Before
public void setup() throws Exception {
mModelHelper = new LauncherModelHelper();
- mModelHelper.initializeData("package_install_state_change_task_data");
+ mModelHelper.createInstallerSession(PENDING_APP_1);
+ mModelHelper.createInstallerSession(PENDING_APP_2);
+
+ LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+ .atWorkspace(0, 0, 1).putApp(TEST_PACKAGE, TEST_ACTIVITY) // 1
+ .atWorkspace(0, 0, 2).putApp(TEST_PACKAGE, TEST_ACTIVITY2) // 2
+ .atWorkspace(0, 0, 3).putApp(TEST_PACKAGE, TEST_ACTIVITY3) // 3
+
+ .atWorkspace(0, 0, 4).putApp(PENDING_APP_1, TEST_ACTIVITY) // 4
+ .atWorkspace(0, 0, 5).putApp(PENDING_APP_1, TEST_ACTIVITY2) // 5
+ .atWorkspace(0, 0, 6).putApp(PENDING_APP_1, TEST_ACTIVITY3) // 6
+ .atWorkspace(0, 0, 7).putWidget(PENDING_APP_1, "pending.widget", 1, 1) // 7
+
+ .atWorkspace(0, 0, 8).putApp(PENDING_APP_2, TEST_ACTIVITY) // 8
+ .atWorkspace(0, 0, 9).putApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
+ .atWorkspace(0, 0, 10).putApp(PENDING_APP_2, TEST_ACTIVITY3); // 10
+
+ mDownloadingApps = IntSet.wrap(4, 5, 6, 7, 8, 9, 10);
+ mModelHelper.setupDefaultLayoutProvider(builder);
+ mModelHelper.loadModelSync();
+ assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
}
@After
@@ -47,36 +77,45 @@
}
@Test
- public void testSessionUpdate_ignore_installed() throws Exception {
- mModelHelper.executeTaskForTest(newTask("app1", 30));
+ public void testSessionUpdate_ignore_installed() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ mModelHelper.getModel().enqueueModelUpdateTask(newTask(TEST_PACKAGE, 30));
- // No shortcuts were updated
- verifyProgressUpdate(0);
+ // No shortcuts were updated
+ verifyProgressUpdate(0);
+ });
}
@Test
- public void testSessionUpdate_shortcuts_updated() throws Exception {
- mModelHelper.executeTaskForTest(newTask("app3", 30));
+ public void testSessionUpdate_shortcuts_updated() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_1, 30));
- verifyProgressUpdate(30, 5, 6, 7);
+ verifyProgressUpdate(30, 4, 5, 6, 7);
+ });
}
@Test
- public void testSessionUpdate_widgets_updated() throws Exception {
- mModelHelper.executeTaskForTest(newTask("app4", 30));
+ public void testSessionUpdate_widgets_updated() {
+ // Run on model executor so that no other task runs in the middle.
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_2, 30));
- verifyProgressUpdate(30, 8, 9);
+ verifyProgressUpdate(30, 8, 9, 10);
+ });
}
- private void verifyProgressUpdate(int progress, Integer... idsUpdated) {
- HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ private void verifyProgressUpdate(int progress, int... idsUpdated) {
+ IntSet updates = IntSet.wrap(idsUpdated);
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
- if (info instanceof WorkspaceItemInfo) {
- assertEquals(updates.contains(info.id) ? progress: 100,
- ((WorkspaceItemInfo) info).getProgressLevel());
+ int expectedProgress = updates.contains(info.id) ? progress
+ : (mDownloadingApps.contains(info.id) ? 0 : 100);
+ if (info instanceof WorkspaceItemInfo wi) {
+ assertEquals(expectedProgress, wi.getProgressLevel());
} else {
- assertEquals(updates.contains(info.id) ? progress: -1,
- ((LauncherAppWidgetInfo) info).installProgress);
+ assertEquals(expectedProgress, ((LauncherAppWidgetInfo) info).installProgress);
}
}
}
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index a81413e..270672f 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -64,8 +64,8 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 165.0px (62.857143dp)\n" +
- "\tcellHeightPx: 235.0px (89.52381dp)\n" +
+ "\tcellWidthPx: 159.0px (60.57143dp)\n" +
+ "\tcellHeightPx: 229.0px (87.2381dp)\n" +
"\tgetCellSize().x: 207.0px (78.85714dp)\n" +
"\tgetCellSize().y: 379.0px (144.38095dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -76,15 +76,16 @@
"\tcellLayoutPaddingPx.bottom: 28.0px (10.666667dp)\n" +
"\ticonSizePx: 147.0px (56.0dp)\n" +
"\ticonTextSizePx: 38.0px (14.476191dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 12.0px (4.571429dp)\n" +
"\tinv.numFolderRows: 4\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 195.0px (74.28571dp)\n" +
"\tfolderCellHeightPx: 230.0px (87.61905dp)\n" +
"\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
"\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -105,7 +106,7 @@
"\tallAppsBorderSpacePxX: 42.0px (16.0dp)\n" +
"\tallAppsBorderSpacePxY: 42.0px (16.0dp)\n" +
"\tnumShownAllAppsColumns: 5\n" +
- "\tallAppsLeftRightPadding: 21.0px (8.0dp)\n" +
+ "\tallAppsLeftRightPadding: 0.0px (0.0dp)\n" +
"\tallAppsLeftRightMargin: 0.0px (0.0dp)\n" +
"\thotseatBarSizePx: 294.0px (112.0dp)\n" +
"\tinv.hotseatColumnSpan: 5\n" +
@@ -138,8 +139,8 @@
"\tworkspacePadding.bottom: 203.0px (77.333336dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 722.0px (275.0476dp)\n" +
- "\tunscaled extraSpace: 722.0px (275.0476dp)\n" +
+ "\textraSpace: 752.0px (286.4762dp)\n" +
+ "\tunscaled extraSpace: 752.0px (286.4762dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -200,8 +201,8 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 165.0px (62.857143dp)\n" +
- "\tcellHeightPx: 235.0px (89.52381dp)\n" +
+ "\tcellWidthPx: 159.0px (60.57143dp)\n" +
+ "\tcellHeightPx: 229.0px (87.2381dp)\n" +
"\tgetCellSize().x: 207.0px (78.85714dp)\n" +
"\tgetCellSize().y: 383.0px (145.90475dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -212,15 +213,16 @@
"\tcellLayoutPaddingPx.bottom: 28.0px (10.666667dp)\n" +
"\ticonSizePx: 147.0px (56.0dp)\n" +
"\ticonTextSizePx: 38.0px (14.476191dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 12.0px (4.571429dp)\n" +
"\tinv.numFolderRows: 4\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 195.0px (74.28571dp)\n" +
"\tfolderCellHeightPx: 230.0px (87.61905dp)\n" +
"\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
"\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -241,7 +243,7 @@
"\tallAppsBorderSpacePxX: 42.0px (16.0dp)\n" +
"\tallAppsBorderSpacePxY: 42.0px (16.0dp)\n" +
"\tnumShownAllAppsColumns: 5\n" +
- "\tallAppsLeftRightPadding: 21.0px (8.0dp)\n" +
+ "\tallAppsLeftRightPadding: 0.0px (0.0dp)\n" +
"\tallAppsLeftRightMargin: 0.0px (0.0dp)\n" +
"\thotseatBarSizePx: 273.0px (104.0dp)\n" +
"\tinv.hotseatColumnSpan: 5\n" +
@@ -274,8 +276,8 @@
"\tworkspacePadding.bottom: 245.0px (93.333336dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 743.0px (283.0476dp)\n" +
- "\tunscaled extraSpace: 743.0px (283.0476dp)\n" +
+ "\textraSpace: 773.0px (294.4762dp)\n" +
+ "\tunscaled extraSpace: 773.0px (294.4762dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -336,7 +338,7 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 158.0px (60.190475dp)\n" +
+ "\tcellWidthPx: 152.0px (57.904762dp)\n" +
"\tcellHeightPx: 166.0px (63.238094dp)\n" +
"\tgetCellSize().x: 368.0px (140.19048dp)\n" +
"\tgetCellSize().y: 193.0px (73.52381dp)\n" +
@@ -355,8 +357,9 @@
"\tfolderCellHeightPx: 205.0px (78.09524dp)\n" +
"\tfolderChildIconSizePx: 131.0px (49.904762dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 9.0px (3.4285715dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 56.0px (21.333334dp)\n" +
"\tfolderFooterHeight: 131.0px (49.904762dp)\n" +
@@ -472,7 +475,7 @@
"\tinv.numColumns: 5\n" +
"\tinv.numSearchContainerColumns: 5\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 158.0px (60.190475dp)\n" +
+ "\tcellWidthPx: 152.0px (57.904762dp)\n" +
"\tcellHeightPx: 166.0px (63.238094dp)\n" +
"\tgetCellSize().x: 393.0px (149.71428dp)\n" +
"\tgetCellSize().y: 180.0px (68.57143dp)\n" +
@@ -491,8 +494,9 @@
"\tfolderCellHeightPx: 192.0px (73.14286dp)\n" +
"\tfolderChildIconSizePx: 123.0px (46.857143dp)\n" +
"\tfolderChildTextSizePx: 32.0px (12.190476dp)\n" +
- "\tfolderChildDrawablePaddingPx: 8.0px (3.047619dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 3.0px (1.1428572dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 53.0px (20.190475dp)\n" +
"\tfolderFooterHeight: 123.0px (46.857143dp)\n" +
@@ -621,15 +625,16 @@
"\tcellLayoutPaddingPx.bottom: 59.0px (29.5dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
"\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 11.0px (5.5dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
"\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -644,7 +649,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 284.0px (142.0dp)\n" +
"\tallAppsCellWidthPx: 252.0px (126.0dp)\n" +
"\tallAppsBorderSpacePxX: 32.0px (16.0dp)\n" +
@@ -758,15 +763,16 @@
"\tcellLayoutPaddingPx.bottom: 59.0px (29.5dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
"\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 11.0px (5.5dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
"\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -781,7 +787,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 284.0px (142.0dp)\n" +
"\tallAppsCellWidthPx: 252.0px (126.0dp)\n" +
"\tallAppsBorderSpacePxX: 32.0px (16.0dp)\n" +
@@ -895,15 +901,16 @@
"\tcellLayoutPaddingPx.bottom: 72.0px (36.0dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
"\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 22.0px (11.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
"\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -918,7 +925,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 316.0px (158.0dp)\n" +
"\tallAppsCellWidthPx: 192.0px (96.0dp)\n" +
"\tallAppsBorderSpacePxX: 16.0px (8.0dp)\n" +
@@ -1032,15 +1039,16 @@
"\tcellLayoutPaddingPx.bottom: 72.0px (36.0dp)\n" +
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
- "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 3\n" +
"\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
"\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 22.0px (11.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
"\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -1055,7 +1063,7 @@
"\tallAppsCloseDuration: 500\n" +
"\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
"\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
- "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+ "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
"\tallAppsCellHeightPx: 316.0px (158.0dp)\n" +
"\tallAppsCellWidthPx: 192.0px (96.0dp)\n" +
"\tallAppsBorderSpacePxX: 16.0px (8.0dp)\n" +
@@ -1162,8 +1170,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 270.0px (102.85714dp)\n" +
"\tgetCellSize().y: 342.0px (130.28572dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1174,15 +1182,16 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -1236,8 +1245,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 478.0px (182.09525dp)\n" +
- "\tunscaled extraSpace: 478.0px (182.09525dp)\n" +
+ "\textraSpace: 498.0px (189.71428dp)\n" +
+ "\tunscaled extraSpace: 498.0px (189.71428dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1303,8 +1312,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 270.0px (102.85714dp)\n" +
"\tgetCellSize().y: 342.0px (130.28572dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1315,15 +1324,16 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -1377,8 +1387,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 478.0px (182.09525dp)\n" +
- "\tunscaled extraSpace: 478.0px (182.09525dp)\n" +
+ "\textraSpace: 498.0px (189.71428dp)\n" +
+ "\tunscaled extraSpace: 498.0px (189.71428dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1444,8 +1454,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 224.0px (85.333336dp)\n" +
"\tgetCellSize().y: 430.0px (163.80952dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1456,15 +1466,16 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -1518,8 +1529,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 829.0px (315.8095dp)\n" +
- "\tunscaled extraSpace: 829.0px (315.8095dp)\n" +
+ "\textraSpace: 849.0px (323.42856dp)\n" +
+ "\tunscaled extraSpace: 849.0px (323.42856dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1581,8 +1592,8 @@
"\tinv.numColumns: 4\n" +
"\tinv.numSearchContainerColumns: 4\n" +
"\tminCellSize: PointF(0.0, 0.0)dp\n" +
- "\tcellWidthPx: 159.0px (60.57143dp)\n" +
- "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+ "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+ "\tcellHeightPx: 218.0px (83.04762dp)\n" +
"\tgetCellSize().x: 224.0px (85.333336dp)\n" +
"\tgetCellSize().y: 430.0px (163.80952dp)\n" +
"\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1593,15 +1604,16 @@
"\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
"\ticonSizePx: 141.0px (53.714287dp)\n" +
"\ticonTextSizePx: 34.0px (12.952381dp)\n" +
- "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+ "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tinv.numFolderRows: 3\n" +
"\tinv.numFolderColumns: 4\n" +
"\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
"\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
"\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
"\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
- "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
"\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
"\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -1655,8 +1667,8 @@
"\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
"\ticonScale: 1.0px (0.3809524dp)\n" +
"\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
- "\textraSpace: 829.0px (315.8095dp)\n" +
- "\tunscaled extraSpace: 829.0px (315.8095dp)\n" +
+ "\textraSpace: 849.0px (323.42856dp)\n" +
+ "\tunscaled extraSpace: 849.0px (323.42856dp)\n" +
"\tmaxEmptySpace: 0.0px (0.0dp)\n" +
"\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
"\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
diff --git a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
index 2a27487..d102397 100644
--- a/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/HotseatWidthCalculationTest.kt
@@ -158,4 +158,25 @@
assertThat(dp.isQsbInline).isFalse()
assertThat(dp.hotseatQsbWidth).isEqualTo(1095)
}
+
+ @Test
+ fun border_space_should_be_zero_when_numHotseatIcons_is_smallerOrEqual_1() {
+ initializeVarsForTablet(isGestureMode = false)
+ windowBounds = WindowBounds(Rect(0, 0, 1800, 2560), Rect(0, 104, 0, 0))
+
+ val numShownHotseatIcons = listOf(-1, 0, 1)
+ for (numHotseatIcons in numShownHotseatIcons) {
+ inv?.numShownHotseatIcons = numHotseatIcons
+
+ val dp = newDP()
+ dp.isTaskbarPresentInApps = true
+
+ assertThat(dp.numShownHotseatIcons).isEqualTo(numHotseatIcons)
+ assertThat(dp.hotseatBorderSpace).isEqualTo(0)
+
+ assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(177)
+ assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(177)
+ assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
new file mode 100644
index 0000000..cd95e99
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.responsive
+
+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 AllAppsSpecsTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpecs["phone"]!!)
+ }
+
+ @Test
+ fun parseValidFile() {
+ val allAppsSpecs =
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
+ assertThat(allAppsSpecs.heightSpecs.size).isEqualTo(1)
+ assertThat(allAppsSpecs.heightSpecs[0].toString())
+ .isEqualTo(
+ "AllAppsSpec(" +
+ "maxAvailableSize=26247, " +
+ "specType=HEIGHT, " +
+ "startPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "gutter=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=true, " +
+ "maxSize=2147483647), " +
+ "cellSize=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=true, " +
+ "maxSize=2147483647)" +
+ ")"
+ )
+
+ assertThat(allAppsSpecs.widthSpecs.size).isEqualTo(1)
+ assertThat(allAppsSpecs.widthSpecs[0].toString())
+ .isEqualTo(
+ "AllAppsSpec(" +
+ "maxAvailableSize=26247, " +
+ "specType=WIDTH, " +
+ "startPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=true, " +
+ "maxSize=2147483647), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=true, " +
+ "maxSize=2147483647), " +
+ "gutter=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=true, " +
+ "maxSize=2147483647), " +
+ "cellSize=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=true, " +
+ "maxSize=2147483647)" +
+ ")"
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_missingTag_throwsError() {
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_1))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_2))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_valueBiggerThan1_throwsError() {
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_3))
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
new file mode 100644
index 0000000..0f12e58
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.responsive
+
+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.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CalculatedAllAppsSpecTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ /**
+ * This test tests:
+ * - (height spec) copy values from workspace
+ * - (width spec) copy values from workspace
+ */
+ @Test
+ fun normalPhone_copiesFromWorkspace() {
+ val deviceSpec = deviceSpecs["phone"]!!
+ initializeVarsForPhone(deviceSpec)
+
+ val availableWidth = deviceSpec.naturalSize.first
+ // Hotseat size is roughly 495px on a real device,
+ // it doesn't need to be precise on unit tests
+ val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
+
+ val workspaceSpecs =
+ WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
+ val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+
+ val allAppsSpecs =
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
+
+ with(allAppsSpecs.getCalculatedWidthSpec(4, availableWidth, widthSpec)) {
+ assertThat(availableSpace).isEqualTo(availableWidth)
+ assertThat(cells).isEqualTo(4)
+ assertThat(startPaddingPx).isEqualTo(widthSpec.startPaddingPx)
+ assertThat(endPaddingPx).isEqualTo(widthSpec.endPaddingPx)
+ assertThat(gutterPx).isEqualTo(widthSpec.gutterPx)
+ assertThat(cellSizePx).isEqualTo(widthSpec.cellSizePx)
+ }
+
+ with(allAppsSpecs.getCalculatedHeightSpec(5, availableHeight, heightSpec)) {
+ assertThat(availableSpace).isEqualTo(availableHeight)
+ assertThat(cells).isEqualTo(5)
+ assertThat(startPaddingPx).isEqualTo(0)
+ assertThat(endPaddingPx).isEqualTo(0)
+ assertThat(gutterPx).isEqualTo(heightSpec.gutterPx)
+ assertThat(cellSizePx).isEqualTo(heightSpec.cellSizePx)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
new file mode 100644
index 0000000..f2a269a
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.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.responsive
+
+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
+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 CalculatedFolderSpecsTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ private val deviceSpec = deviceSpecs["phone"]!!
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpec)
+ }
+
+ @Test
+ fun validate_matchWidthWorkspace() {
+ val columns = 6
+
+ // Loading workspace specs
+ val resourceHelperWorkspace = TestResourceHelper(context!!, R.xml.valid_workspace_file)
+ val workspaceSpecs = WorkspaceSpecs.create(resourceHelperWorkspace)
+
+ // Loading folders specs
+ val resourceHelperFolder = TestResourceHelper(context!!, R.xml.valid_folders_specs)
+ val folderSpecs = FolderSpecs.create(resourceHelperFolder)
+
+ assertThat(folderSpecs.widthSpecs.size).isEqualTo(2)
+ assertThat(folderSpecs.widthSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
+ assertThat(folderSpecs.widthSpecs[1].cellSize.matchWorkspace).isEqualTo(false)
+
+ // Validate width spec <= 800
+ var availableWidth = deviceSpec.naturalSize.first
+ var calculatedWorkspace = workspaceSpecs.getCalculatedWidthSpec(columns, availableWidth)
+ var calculatedWidthFolderSpec =
+ folderSpecs.getCalculatedWidthSpec(columns, availableWidth, calculatedWorkspace)
+ with(calculatedWidthFolderSpec) {
+ assertThat(availableSpace).isEqualTo(availableWidth)
+ assertThat(cells).isEqualTo(columns)
+ assertThat(startPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(endPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(gutterPx).isEqualTo(16.dpToPx())
+ assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
+ }
+
+ // Validate width spec > 800
+ availableWidth = 2000.dpToPx()
+ calculatedWorkspace = workspaceSpecs.getCalculatedWidthSpec(columns, availableWidth)
+ calculatedWidthFolderSpec =
+ folderSpecs.getCalculatedWidthSpec(columns, availableWidth, calculatedWorkspace)
+ with(calculatedWidthFolderSpec) {
+ assertThat(availableSpace).isEqualTo(availableWidth)
+ assertThat(cells).isEqualTo(columns)
+ assertThat(startPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(endPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(gutterPx).isEqualTo(16.dpToPx())
+ assertThat(cellSizePx).isEqualTo(102.dpToPx())
+ }
+ }
+
+ @Test
+ fun validate_matchHeightWorkspace() {
+ // Hotseat is roughly 495px on a real device, it doesn't need to be precise on unit tests
+ val hotseatSize = 495
+ val statusBarHeight = deviceSpec.statusBarNaturalPx
+ val availableHeight = deviceSpec.naturalSize.second - statusBarHeight - hotseatSize
+ val rows = 5
+
+ // Loading workspace specs
+ val resourceHelperWorkspace = TestResourceHelper(context!!, R.xml.valid_workspace_file)
+ val workspaceSpecs = WorkspaceSpecs.create(resourceHelperWorkspace)
+
+ // Loading folders specs
+ val resourceHelperFolder = TestResourceHelper(context!!, R.xml.valid_folders_specs)
+ val folderSpecs = FolderSpecs.create(resourceHelperFolder)
+
+ assertThat(folderSpecs.heightSpecs.size).isEqualTo(1)
+ assertThat(folderSpecs.heightSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
+
+ // Validate height spec
+ val calculatedWorkspace = workspaceSpecs.getCalculatedHeightSpec(rows, availableHeight)
+ val calculatedFolderSpec =
+ folderSpecs.getCalculatedHeightSpec(rows, availableHeight, calculatedWorkspace)
+ with(calculatedFolderSpec) {
+ assertThat(availableSpace).isEqualTo(availableHeight)
+ assertThat(cells).isEqualTo(rows)
+ assertThat(startPaddingPx).isEqualTo(24.dpToPx())
+ assertThat(endPaddingPx).isEqualTo(64.dpToPx())
+ assertThat(gutterPx).isEqualTo(16.dpToPx())
+ assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
new file mode 100644
index 0000000..0ecf7ba
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.responsive
+
+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.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CalculatedHotseatSpecTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ /**
+ * This test tests:
+ * - (height spec) gets the correct breakpoint from the XML - skips the first breakpoint
+ */
+ @Test
+ fun normalPhone_returnsSecondBreakpointSpec() {
+ val deviceSpec = deviceSpecs["phone"]!!
+ initializeVarsForPhone(deviceSpec)
+
+ // Hotseat uses the whole device height
+ val availableHeight = deviceSpec.naturalSize.second
+
+ val hotseatSpecs =
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+ val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+
+ assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+ assertThat(heightSpec.hotseatQsbSpace).isEqualTo(95)
+ }
+
+ /**
+ * This test tests:
+ * - (height spec) gets the correct breakpoint from the XML - use the first breakpoint
+ */
+ @Test
+ fun smallPhone_returnsFirstBreakpointSpec() {
+ val deviceSpec = deviceSpecs["phone"]!!
+ deviceSpec.densityDpi = 540 // larger display size
+ initializeVarsForPhone(deviceSpec)
+
+ // Hotseat uses the whole device height
+ val availableHeight = deviceSpec.naturalSize.second
+
+ val hotseatSpecs =
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+ val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+
+ assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+ assertThat(heightSpec.hotseatQsbSpace).isEqualTo(81)
+ }
+}
diff --git a/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
rename to tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
index 7f03ba2..0af694e 100644
--- a/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.workspace
+package com.android.launcher3.responsive
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -49,7 +49,7 @@
val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
val workspaceSpecs =
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
@@ -86,7 +86,7 @@
val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
val workspaceSpecs =
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
new file mode 100644
index 0000000..4b05949
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
@@ -0,0 +1,251 @@
+/*
+ * 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.responsive
+
+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.responsive.ResponsiveSpec.SpecType
+import com.android.launcher3.tests.R
+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 FolderSpecsTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpecs["tablet"]!!)
+ }
+
+ @Test
+ fun parseValidFile() {
+ val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+
+ val sizeSpec16 = SizeSpec(16f.dpToPx())
+ val widthSpecsExpected =
+ listOf(
+ FolderSpec(
+ maxAvailableSize = 800.dpToPx(),
+ specType = SpecType.WIDTH,
+ startPadding = sizeSpec16,
+ endPadding = sizeSpec16,
+ gutter = sizeSpec16,
+ cellSize = SizeSpec(matchWorkspace = true)
+ ),
+ FolderSpec(
+ maxAvailableSize = 9999.dpToPx(),
+ specType = SpecType.WIDTH,
+ startPadding = sizeSpec16,
+ endPadding = sizeSpec16,
+ gutter = sizeSpec16,
+ cellSize = SizeSpec(102f.dpToPx())
+ )
+ )
+
+ val heightSpecsExpected =
+ FolderSpec(
+ maxAvailableSize = 9999.dpToPx(),
+ specType = SpecType.HEIGHT,
+ startPadding = SizeSpec(24f.dpToPx()),
+ endPadding = SizeSpec(64f.dpToPx()),
+ gutter = sizeSpec16,
+ cellSize = SizeSpec(matchWorkspace = true)
+ )
+
+ assertThat(folderSpecs.widthSpecs.size).isEqualTo(widthSpecsExpected.size)
+ assertThat(folderSpecs.widthSpecs[0]).isEqualTo(widthSpecsExpected[0])
+ assertThat(folderSpecs.widthSpecs[1]).isEqualTo(widthSpecsExpected[1])
+
+ assertThat(folderSpecs.heightSpecs.size).isEqualTo(1)
+ assertThat(folderSpecs.heightSpecs[0]).isEqualTo(heightSpecsExpected)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_missingTag_throwsError() {
+ val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_1)
+ FolderSpecs.create(resourceHelper)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
+ val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_2)
+ FolderSpecs.create(resourceHelper)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_valueBiggerThan1_throwsError() {
+ val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_3)
+ FolderSpecs.create(resourceHelper)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_missingSpecs_throwsError() {
+ val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_4)
+ FolderSpecs.create(resourceHelper)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_missingWidthBreakpoint_throwsError() {
+ val availableSpace = 900.dpToPx()
+ val cells = 3
+
+ val workspaceSpec =
+ WorkspaceSpec(
+ maxAvailableSize = availableSpace,
+ specType = SpecType.WIDTH,
+ startPadding = SizeSpec(fixedSize = 10f),
+ endPadding = SizeSpec(fixedSize = 10f),
+ gutter = SizeSpec(fixedSize = 10f),
+ cellSize = SizeSpec(fixedSize = 10f)
+ )
+ val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
+
+ val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_5)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_missingHeightBreakpoint_throwsError() {
+ val availableSpace = 900.dpToPx()
+ val cells = 3
+
+ val workspaceSpec =
+ WorkspaceSpec(
+ maxAvailableSize = availableSpace,
+ specType = SpecType.HEIGHT,
+ startPadding = SizeSpec(fixedSize = 10f),
+ endPadding = SizeSpec(fixedSize = 10f),
+ gutter = SizeSpec(fixedSize = 10f),
+ cellSize = SizeSpec(fixedSize = 10f)
+ )
+ val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
+
+ val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_5)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ }
+
+ @Test
+ fun retrievesCalculatedWidthSpec() {
+ val availableSpace = 800.dpToPx()
+ val cells = 3
+
+ val workspaceSpec =
+ WorkspaceSpec(
+ maxAvailableSize = availableSpace,
+ specType = SpecType.WIDTH,
+ startPadding = SizeSpec(fixedSize = 10f),
+ endPadding = SizeSpec(fixedSize = 10f),
+ gutter = SizeSpec(fixedSize = 10f),
+ cellSize = SizeSpec(fixedSize = 10f)
+ )
+ val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
+
+ val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ val calculatedWidthSpec =
+ folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
+
+ assertThat(calculatedWidthSpec.cells).isEqualTo(cells)
+ assertThat(calculatedWidthSpec.availableSpace).isEqualTo(availableSpace)
+ assertThat(calculatedWidthSpec.startPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedWidthSpec.endPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedWidthSpec.gutterPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedWidthSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun retrievesCalculatedWidthSpec_invalidCalculatedWorkspaceSpecType_throwsError() {
+ val availableSpace = 10.dpToPx()
+ val cells = 3
+
+ val workspaceSpec =
+ WorkspaceSpec(
+ maxAvailableSize = availableSpace,
+ specType = SpecType.HEIGHT,
+ startPadding = SizeSpec(fixedSize = 10f),
+ endPadding = SizeSpec(fixedSize = 10f),
+ gutter = SizeSpec(fixedSize = 10f),
+ cellSize = SizeSpec(fixedSize = 10f)
+ )
+ val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
+
+ val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ }
+
+ @Test
+ fun retrievesCalculatedHeightSpec() {
+ val availableSpace = 700.dpToPx()
+ val cells = 3
+
+ val workspaceSpec =
+ WorkspaceSpec(
+ maxAvailableSize = availableSpace,
+ specType = SpecType.HEIGHT,
+ startPadding = SizeSpec(fixedSize = 10f),
+ endPadding = SizeSpec(fixedSize = 10f),
+ gutter = SizeSpec(fixedSize = 10f),
+ cellSize = SizeSpec(fixedSize = 10f)
+ )
+ val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
+
+ val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ val calculatedHeightSpec =
+ folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
+
+ assertThat(calculatedHeightSpec.cells).isEqualTo(cells)
+ assertThat(calculatedHeightSpec.availableSpace).isEqualTo(availableSpace)
+ assertThat(calculatedHeightSpec.startPaddingPx).isEqualTo(24.dpToPx())
+ assertThat(calculatedHeightSpec.endPaddingPx).isEqualTo(64.dpToPx())
+ assertThat(calculatedHeightSpec.gutterPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedHeightSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun retrievesCalculatedHeightSpec_invalidCalculatedWorkspaceSpecType_throwsError() {
+ val availableSpace = 10.dpToPx()
+ val cells = 3
+
+ val workspaceSpec =
+ WorkspaceSpec(
+ maxAvailableSize = availableSpace,
+ specType = SpecType.WIDTH,
+ startPadding = SizeSpec(fixedSize = 10f),
+ endPadding = SizeSpec(fixedSize = 10f),
+ gutter = SizeSpec(fixedSize = 10f),
+ cellSize = SizeSpec(fixedSize = 10f)
+ )
+ val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
+
+ val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt b/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
new file mode 100644
index 0000000..c764e47
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.responsive
+
+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.android.systemui.util.dpToPx
+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 HotseatSpecsTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpecs["phone"]!!)
+ }
+
+ @Test
+ fun parseValidFile() {
+ val hotseatSpecs =
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+ assertThat(hotseatSpecs.specs.size).isEqualTo(2)
+
+ val expectedSpecs =
+ listOf(
+ HotseatSpec(
+ maxAvailableSize = 847.dpToPx(),
+ specType = ResponsiveSpec.SpecType.HEIGHT,
+ hotseatQsbSpace = SizeSpec(24f.dpToPx())
+ ),
+ HotseatSpec(
+ maxAvailableSize = 9999.dpToPx(),
+ specType = ResponsiveSpec.SpecType.HEIGHT,
+ hotseatQsbSpace = SizeSpec(36f.dpToPx())
+ ),
+ )
+
+ assertThat(hotseatSpecs.specs.size).isEqualTo(expectedSpecs.size)
+ assertThat(hotseatSpecs.specs[0]).isEqualTo(expectedSpecs[0])
+ assertThat(hotseatSpecs.specs[1]).isEqualTo(expectedSpecs[1])
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_hotseat_file_case_1))
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
new file mode 100644
index 0000000..8ca07c6
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.responsive
+
+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.google.common.truth.Truth.assertThat
+import kotlin.math.roundToInt
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SizeSpecTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpecs["phone"]!!)
+ }
+
+ @Test
+ fun valid_values() {
+ val combinations =
+ listOf(
+ SizeSpec(100f, 0f, 0f, false),
+ SizeSpec(0f, 1f, 0f, false),
+ SizeSpec(0f, 0f, 1f, false),
+ SizeSpec(0f, 0f, 0f, false),
+ SizeSpec(0f, 0f, 0f, true),
+ SizeSpec(100f, 0f, 0f, false, 100),
+ SizeSpec(0f, 1f, 0f, false, 100),
+ SizeSpec(0f, 0f, 1f, false, 100),
+ SizeSpec(0f, 0f, 0f, false, 100),
+ SizeSpec(0f, 0f, 0f, true, 100)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.isValid()).isEqualTo(true)
+ }
+ }
+
+ @Test
+ fun validate_getCalculatedValue() {
+ val availableSpace = 100
+ val matchWorkspaceValue = 101
+ val combinations =
+ listOf(
+ SizeSpec(100f) to 100,
+ SizeSpec(ofAvailableSpace = .5f) to (availableSpace * .5f).roundToInt(),
+ SizeSpec(ofRemainderSpace = .5f) to 0,
+ SizeSpec(matchWorkspace = true) to matchWorkspaceValue,
+ // Restricts max size up to 10 (calculated value > 10)
+ SizeSpec(100f, maxSize = 10) to 10,
+ SizeSpec(ofAvailableSpace = .5f, maxSize = 10) to 10,
+ SizeSpec(ofRemainderSpace = .5f, maxSize = 10) to 0,
+ SizeSpec(matchWorkspace = true, maxSize = 10) to 10
+ )
+
+ for ((sizeSpec, expectedValue) in combinations) {
+ val value = sizeSpec.getCalculatedValue(availableSpace, matchWorkspaceValue)
+ assertThat(value).isEqualTo(expectedValue)
+ }
+ }
+
+ @Test
+ fun validate_getRemainderSpaceValue() {
+ val remainderSpace = 100
+ val defaultValue = 50
+ val combinations =
+ listOf(
+ SizeSpec(100f) to defaultValue,
+ SizeSpec(ofAvailableSpace = .5f) to defaultValue,
+ SizeSpec(ofRemainderSpace = .5f) to (remainderSpace * .5f).roundToInt(),
+ SizeSpec(matchWorkspace = true) to defaultValue,
+ // Restricts max size up to 10 (defaultValue > 10)
+ SizeSpec(100f, maxSize = 10) to 10,
+ SizeSpec(ofAvailableSpace = .5f, maxSize = 10) to 10,
+ SizeSpec(ofRemainderSpace = .5f, maxSize = 10) to 10,
+ SizeSpec(matchWorkspace = true, maxSize = 10) to 10,
+ )
+
+ for ((sizeSpec, expectedValue) in combinations) {
+ val value = sizeSpec.getRemainderSpaceValue(remainderSpace, defaultValue)
+ assertThat(value).isEqualTo(expectedValue)
+ }
+ }
+
+ @Test
+ fun multiple_values_assigned() {
+ val combinations =
+ listOf(
+ SizeSpec(1f, 1f, 0f, false),
+ SizeSpec(1f, 0f, 1f, false),
+ SizeSpec(1f, 0f, 0f, true),
+ SizeSpec(0f, 1f, 1f, false),
+ SizeSpec(0f, 1f, 0f, true),
+ SizeSpec(0f, 0f, 1f, true),
+ SizeSpec(1f, 1f, 1f, true)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.isValid()).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun invalid_values() {
+ val combinations =
+ listOf(
+ SizeSpec(-1f, 0f, 0f, false),
+ SizeSpec(0f, 1.1f, 0f, false),
+ SizeSpec(0f, -0.1f, 0f, false),
+ SizeSpec(0f, 0f, 1.1f, false),
+ SizeSpec(0f, 0f, -0.1f, false),
+ SizeSpec(0f, 0f, 0f, false, -10),
+ SizeSpec(50f, 0f, 0f, false, 10)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.isValid()).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun onlyFixedSize() {
+ assertThat(SizeSpec(fixedSize = 16f).onlyFixedSize()).isEqualTo(true)
+
+ val combinations =
+ listOf(
+ SizeSpec(0f, 1.1f, 0f, false),
+ SizeSpec(0f, 0f, 1.1f, false),
+ SizeSpec(0f, 0f, 0f, true)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.onlyFixedSize()).isEqualTo(false)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
new file mode 100644
index 0000000..0364069
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.responsive
+
+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.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ assertThat(workspaceSpecs.heightSpecs.size).isEqualTo(3)
+ assertThat(workspaceSpecs.heightSpecs[0].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=1533, " +
+ "specType=HEIGHT, " +
+ "startPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "endPadding=SizeSpec(fixedSize=84.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "cellSize=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.15808, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647)" +
+ ")"
+ )
+ assertThat(workspaceSpecs.heightSpecs[1].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=1607, " +
+ "specType=HEIGHT, " +
+ "startPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=1.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "cellSize=SizeSpec(fixedSize=273.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647)" +
+ ")"
+ )
+ assertThat(workspaceSpecs.heightSpecs[2].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=26247, " +
+ "specType=HEIGHT, " +
+ "startPadding=SizeSpec(fixedSize=21.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=1.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "cellSize=SizeSpec(fixedSize=273.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647)" +
+ ")"
+ )
+ assertThat(workspaceSpecs.widthSpecs.size).isEqualTo(1)
+ assertThat(workspaceSpecs.widthSpecs[0].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=26247, " +
+ "specType=WIDTH, " +
+ "startPadding=SizeSpec(fixedSize=58.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "endPadding=SizeSpec(fixedSize=58.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647), " +
+ "cellSize=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.25, " +
+ "matchWorkspace=false, " +
+ "maxSize=2147483647)" +
+ ")"
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_missingTag_throwsError() {
+ WorkspaceSpecs.create(
+ TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_1)
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
+ WorkspaceSpecs.create(
+ TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_2)
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_valueBiggerThan1_throwsError() {
+ WorkspaceSpecs.create(
+ TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_3)
+ )
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_matchWorkspace_true_throwsError() {
+ WorkspaceSpecs.create(
+ TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_4)
+ )
+ }
+}
diff --git a/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java b/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
index 7e9d9da..c7431f2 100644
--- a/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
+++ b/tests/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncherTest.java
@@ -184,7 +184,7 @@
mStartPoint = icon.getVisibleCenter();
mEndPoint = new Point(mStartPoint.x, mStartPoint.y);
mLauncher.sendPointer(mDownTime, mDownTime, ACTION_DOWN, mStartPoint,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
assertThat(findObjectByResourceName("popup_container")).isNotNull();
return appName;
}
@@ -206,7 +206,7 @@
mStartPoint = icon.getVisibleCenter();
mEndPoint = new Point(mStartPoint.x, mStartPoint.y);
mLauncher.sendPointer(mDownTime, mDownTime, ACTION_DOWN, mStartPoint,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
assertThat(findObjectByResourceName("popup_container")).isNotNull();
return appName;
}
@@ -214,12 +214,12 @@
private void moveAppToCenterOfScreen() {
mEndPoint.set(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2);
mLauncher.movePointer(mDownTime, SystemClock.uptimeMillis(), DRAG_TIME_MS, true,
- mStartPoint, mEndPoint, LauncherInstrumentation.GestureScope.INSIDE);
+ mStartPoint, mEndPoint, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
}
private void dropApp() {
mLauncher.sendPointer(mDownTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
- mEndPoint, LauncherInstrumentation.GestureScope.INSIDE);
+ mEndPoint, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
}
private void removeAppByName(String appName) {
diff --git a/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
new file mode 100644
index 0000000..15db1d8
--- /dev/null
+++ b/tests/src/com/android/launcher3/tapl/TaplUtilityTests.java
@@ -0,0 +1,38 @@
+/*
+ * 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.tapl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class TaplUtilityTests {
+
+ @Test
+ public void testNewStringWithRegex() {
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Store has 7 notifications").matches());
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Store!").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("play store").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("").matches());
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play \n Store").matches());
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
index d3ce67c..81a59b9 100644
--- a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
+++ b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
@@ -69,7 +69,10 @@
mView.setBackgroundColor(Color.BLUE);
setContentView(mView);
- registerReceiver(mCommandReceiver, new IntentFilter(mAction + SUFFIX_COMMAND));
+ registerReceiver(
+ mCommandReceiver,
+ new IntentFilter(mAction + SUFFIX_COMMAND),
+ RECEIVER_EXPORTED);
}
protected void addButton(String title, String method) {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 604fe42..5240e6a 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,6 +17,7 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -49,11 +50,8 @@
import androidx.test.uiautomator.Until;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.HomeAppIcon;
@@ -64,10 +62,9 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.util.rule.FailureWatcher;
-import com.android.launcher3.util.rule.LauncherActivityRule;
import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.ShellCommandRule;
@@ -82,10 +79,6 @@
import org.junit.rules.TestRule;
import java.io.IOException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -102,12 +95,12 @@
public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
- public static final long DEFAULT_UI_TIMEOUT = 10000;
+ public static final long DEFAULT_UI_TIMEOUT = TestUtil.DEFAULT_UI_TIMEOUT;
private static final String TAG = "AbstractLauncherUiTest";
private static boolean sDumpWasGenerated = false;
private static boolean sActivityLeakReported = false;
- private static boolean sSeenKeygard = false;
+ private static boolean sSeenKeyguard = false;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
@@ -188,8 +181,6 @@
mLauncher.setOnLauncherCrashed(() -> mLauncherPid = 0);
}
- protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
-
@Rule
public ShellCommandRule mDisableHeadsUpNotification =
ShellCommandRule.disableHeadsUpNotification();
@@ -209,18 +200,13 @@
mTargetContext.unregisterReceiver(broadcastReceiver);
}
- // Annotation for tests that need to be run in portrait and landscape modes.
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- protected @interface PortraitLandscape {
- }
-
protected TestRule getRulesInsideActivityMonitor() {
- final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
+ final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
+ Launcher.ACTIVITY_TRACKER::getCreatedActivity);
final RuleChain inner = RuleChain
.outerRule(new PortraitLandscapeRunner(this))
- .around(viewCaptureRule)
- .around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
+ .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
+ .around(viewCaptureRule);
return TestHelpers.isInLauncherProcess()
? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
@@ -231,7 +217,6 @@
public TestRule mOrderSensitiveRules = RuleChain
.outerRule(new SamplerRule())
.around(new TestStabilityRule())
- .around(mActivityMonitor)
.around(getRulesInsideActivityMonitor());
public UiDevice getDevice() {
@@ -276,9 +261,9 @@
}
private static void verifyKeyguardInvisible() {
- final boolean keyguardAlreadyVisible = sSeenKeygard;
+ final boolean keyguardAlreadyVisible = sSeenKeyguard;
- sSeenKeygard = sSeenKeygard
+ sSeenKeyguard = sSeenKeyguard
|| !TestHelpers.wait(
Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000);
@@ -286,7 +271,7 @@
"Keyguard is visible, which is likely caused by a crash in SysUI, seeing keyguard"
+ " for the first time = "
+ !keyguardAlreadyVisible,
- sSeenKeygard);
+ sSeenKeyguard);
}
@After
@@ -303,46 +288,20 @@
}
}
- protected void clearLauncherData() {
- mLauncher.clearLauncherData();
- mLauncher.waitForLauncherInitialized();
+ protected void reinitializeLauncherData() {
+ reinitializeLauncherData(false);
}
- /**
- * Removes all icons from homescreen and hotseat.
- */
- public void clearHomescreen() {
- LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
- resetLoaderState();
- }
-
- protected void resetLoaderState() {
- try {
- mMainThreadExecutor.execute(
- () -> LauncherAppState.getInstance(
- mTargetContext).getModel().forceReload());
- } catch (Throwable t) {
- throw new IllegalArgumentException(t);
+ protected void reinitializeLauncherData(boolean clearWorkspace) {
+ if (clearWorkspace) {
+ mLauncher.clearLauncherData();
+ } else {
+ mLauncher.reinitializeLauncherData();
}
mLauncher.waitForLauncherInitialized();
}
/**
- * Adds {@param item} on the homescreen on the 0th screen
- */
- public void addItemToScreen(ItemInfo item) {
- WidgetUtils.addItemToScreen(item, mTargetContext);
- resetLoaderState();
-
- // Launch the home activity
- mDevice.pressHome();
- mLauncher.waitForLauncherInitialized();
- }
-
- /**
* Runs the callback on the UI thread and returns the result.
*/
protected <T> T getOnUiThread(final Callable<T> callback) {
@@ -360,7 +319,7 @@
protected <T> T getFromLauncher(Function<Launcher, T> f) {
if (!TestHelpers.isInLauncherProcess()) return null;
- return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity()));
+ return getOnUiThread(() -> f.apply(Launcher.ACTIVITY_TRACKER.getCreatedActivity()));
}
protected void executeOnLauncher(Consumer<Launcher> f) {
@@ -648,6 +607,8 @@
protected HomeAppIcon createShortcutIfNotExist(String name, int cellX, int cellY) {
HomeAppIcon homeAppIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
+ Log.d(ICON_MISSING, "homeAppIcon: " + homeAppIcon + " name: " + name +
+ " cell: " + cellX + ", " + cellY);
if (homeAppIcon == null) {
HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
allApps.freeze();
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index fdba4eb..ba17fdc 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -18,6 +18,8 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
+import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
import static org.junit.Assert.assertEquals;
@@ -79,7 +81,6 @@
mContext = new ActivityContextWrapper(getApplicationContext());
mBubbleTextView = new BubbleTextView(mContext);
mBubbleTextView.reset();
- mBubbleTextView.setDisplayAllApps();
BubbleTextView testView = new BubbleTextView(mContext);
testView.setTypeface(Typeface.MONOSPACE);
@@ -104,6 +105,7 @@
public void testEmptyString_flagOn() {
try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
mItemInfoWithIcon.title = EMPTY_STRING;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
@@ -118,6 +120,7 @@
public void testEmptyString_flagOff() {
try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
mItemInfoWithIcon.title = EMPTY_STRING;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
@@ -134,6 +137,7 @@
// test string: "Battery Stats"
mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -149,6 +153,7 @@
// test string: "Battery Stats"
mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -164,6 +169,7 @@
// test string: "flutterappflorafy"
mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -179,6 +185,7 @@
// test string: "flutterappflorafy"
mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -194,6 +201,7 @@
// test string: "System UWB Field Test"
mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -209,6 +217,7 @@
// test string: "System UWB Field Test"
mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -224,6 +233,7 @@
// test string: "LEGO®Builder"
mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -239,6 +249,7 @@
// test string: "LEGO®Builder"
mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
mBubbleTextView.measure(mLimitedWidth, 0);
mBubbleTextView.onPreDraw();
@@ -291,4 +302,20 @@
breakPoints);
assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
}
+
+ @Test
+ public void testEnsurePredictionRowIsOneLine() {
+ try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
+ // test string: "Battery Stats"
+ mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, 0);
+ mBubbleTextView.onPreDraw();
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 266f0ae..f0875f8 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -9,10 +9,21 @@
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
-class PortraitLandscapeRunner implements TestRule {
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+public class PortraitLandscapeRunner implements TestRule {
private static final String TAG = "PortraitLandscapeRunner";
private AbstractLauncherUiTest mTest;
+ // Annotation for tests that need to be run in portrait and landscape modes.
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface PortraitLandscape {
+ }
+
public PortraitLandscapeRunner(AbstractLauncherUiTest test) {
mTest = test;
}
@@ -20,7 +31,7 @@
@Override
public Statement apply(Statement base, Description description) {
if (!TestHelpers.isInLauncherProcess() ||
- description.getAnnotation(AbstractLauncherUiTest.PortraitLandscape.class) == null) {
+ description.getAnnotation(PortraitLandscape.class) == null) {
return base;
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 0b10603..45b01f4 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,12 +18,15 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
@@ -54,10 +57,13 @@
import com.android.launcher3.tapl.HomeAppIconMenuItem;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.TISBindRule;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.WidgetsRecyclerView;
@@ -94,7 +100,12 @@
}
public static void initialize(AbstractLauncherUiTest test) throws Exception {
- test.clearLauncherData();
+ initialize(test, false);
+ }
+
+ public static void initialize(
+ AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
+ test.reinitializeLauncherData(clearWorkspace);
test.mDevice.pressHome();
test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
test.waitForState("Launcher internal state didn't switch to Home",
@@ -147,7 +158,6 @@
}
@Test
- @ScreenRecord
public void testPressHomeOnAllAppsContextMenu() throws Exception {
final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
allApps.freeze();
@@ -221,7 +231,18 @@
@PortraitLandscape
public void testAllAppsSwitchToWorkspace() {
assertNotNull("switchToWorkspace() returned null",
- mLauncher.getWorkspace().switchToAllApps().switchToWorkspace());
+ mLauncher.getWorkspace().switchToAllApps()
+ .switchToWorkspace(/* swipeDown= */ true));
+ assertTrue("Launcher internal state is not Workspace",
+ isInState(() -> LauncherState.NORMAL));
+ }
+
+ @Test
+ @PortraitLandscape
+ public void testAllAppsSwipeUpToWorkspace() {
+ assertNotNull("testAllAppsSwipeUpToWorkspace() returned null",
+ mLauncher.getWorkspace().switchToAllApps()
+ .switchToWorkspace(/* swipeDown= */ false));
assertTrue("Launcher internal state is not Workspace",
isInState(() -> LauncherState.NORMAL));
}
@@ -245,7 +266,7 @@
LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
.atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
- clearLauncherData();
+ reinitializeLauncherData();
final Workspace workspace = mLauncher.getWorkspace();
@@ -310,6 +331,8 @@
}
@Test
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/293191790
+ @ScreenRecord
@PortraitLandscape
public void testWidgets() throws Exception {
// Test opening widgets.
@@ -371,6 +394,28 @@
}
}
+ @Test
+ public void testLaunchHomeScreenMenuItem() {
+ // Drag the test app icon to home screen and open short cut menu from the icon
+ final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ allApps.freeze();
+ try {
+ allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
+ final AppIconMenu menu = mLauncher.getWorkspace().getWorkspaceAppIcon(
+ APP_NAME).openDeepShortcutMenu();
+
+ executeOnLauncher(
+ launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+ isOptionsPopupVisible(launcher)));
+
+ final AppIconMenuItem menuItem = menu.getMenuItem(1);
+ assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
+ menuItem.launch(getAppPackageName());
+ } finally {
+ allApps.unfreeze();
+ }
+ }
+
@PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
@@ -503,19 +548,16 @@
private void verifyAppUninstalledFromAllApps(Workspace workspace, String appName) {
final HomeAllApps allApps = workspace.switchToAllApps();
- allApps.freeze();
- try {
- assertNull(appName + " app was found on all apps after being uninstalled",
- allApps.tryGetAppIcon(appName));
- } finally {
- allApps.unfreeze();
- }
+ Wait.atMost(appName + " app was found on all apps after being uninstalled",
+ () -> allApps.tryGetAppIcon(appName) == null,
+ DEFAULT_UI_TIMEOUT, mLauncher);
}
- @Ignore("b/256615483")
@Test
@PortraitLandscape
- @PlatinumTest(focusArea = "launcher")
+ // TODO(b/293944634): Remove Screenrecord after flaky debug, and add
+ // @PlatinumTest(focusArea = "launcher") back
+ @ScreenRecord
public void testUninstallFromWorkspace() throws Exception {
installDummyAppAndWaitForUIUpdate();
try {
@@ -527,7 +569,6 @@
}
@Test
- @ScreenRecord // b/258071914
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
public void testUninstallFromAllApps() throws Exception {
@@ -536,7 +577,6 @@
Workspace workspace = mLauncher.getWorkspace();
final HomeAllApps allApps = workspace.switchToAllApps();
workspace = allApps.getAppIcon(DUMMY_APP_NAME).uninstall();
- waitForLauncherUIUpdate();
verifyAppUninstalledFromAllApps(workspace, DUMMY_APP_NAME);
} finally {
TestUtil.uninstallDummyApp();
@@ -560,7 +600,7 @@
allApps.unfreeze();
}
// Reset the workspace for the next shortcut creation.
- initialize(this);
+ initialize(this, true);
endTime = SystemClock.uptimeMillis();
elapsedTime = endTime - startTime;
Log.d("testDragAppIconToWorkspaceCellTime",
@@ -579,11 +619,19 @@
}
}
+ /**
+ * Adds three icons to the workspace and removes one of them by dragging to uninstall.
+ */
@Test
@ScreenRecord // b/241821721
@PlatinumTest(focusArea = "launcher")
- public void getIconsPosition_afterIconRemoved_notContained() throws IOException {
+ public void uninstallWorkspaceIcon() throws IOException {
Point[] gridPositions = getCornersAndCenterPositions();
+ StringBuilder sb = new StringBuilder();
+ for (Point p : gridPositions) {
+ sb.append(p).append(", ");
+ }
+ Log.d(ICON_MISSING, "allGridPositions: " + sb);
createShortcutIfNotExist(STORE_APP_NAME, gridPositions[0]);
createShortcutIfNotExist(MAPS_APP_NAME, gridPositions[1]);
installDummyAppAndWaitForUIUpdate();
@@ -598,6 +646,10 @@
mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
+ // Debug for b/288944469 I want to test if we are not waiting enough after removing
+ // the icon to request the list of icons again, since the items are not removed
+ // immediately. This should reduce the flake rate
+ SystemClock.sleep(500);
Map<String, Point> finalPositions =
mLauncher.getWorkspace().getWorkspaceIconsPositions();
assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
@@ -677,8 +729,8 @@
HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
allApps.freeze();
try {
- HomeAppIcon icon = allApps.getAppIcon(APP_NAME);
- assertEquals("Wrong app icon name.", icon.getIconName(), APP_NAME);
+ // getAppIcon() already verifies that the icon is not null and is the correct icon name.
+ allApps.getAppIcon(APP_NAME);
} finally {
allApps.unfreeze();
}
diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
index 083f580..4b2bade 100644
--- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java
+++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
@@ -15,11 +15,14 @@
*/
package com.android.launcher3.ui;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static android.os.Process.myUserHandle;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.util.TestUtil.getOnUiThread;
+
+import android.app.Instrumentation;
import android.content.ComponentName;
-import android.os.Process;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -29,7 +32,6 @@
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetManagerHelper;
-import java.util.concurrent.Callable;
import java.util.function.Function;
public class TestViewHelpers {
@@ -38,23 +40,16 @@
/**
* Finds a widget provider which can fit on the home screen.
*
- * @param test test suite.
* @param hasConfigureScreen if true, a provider with a config screen is returned.
*/
- public static LauncherAppWidgetProviderInfo findWidgetProvider(AbstractLauncherUiTest test,
- final boolean hasConfigureScreen) {
- LauncherAppWidgetProviderInfo info =
- test.getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
- @Override
- public LauncherAppWidgetProviderInfo call() throws Exception {
- ComponentName cn = new ComponentName(getInstrumentation().getContext(),
- hasConfigureScreen ? AppWidgetWithConfig.class
- : AppWidgetNoConfig.class);
- Log.d(TAG, "findWidgetProvider componentName=" + cn.flattenToString());
- return new WidgetManagerHelper(getTargetContext())
- .findProvider(cn, Process.myUserHandle());
- }
- });
+ public static LauncherAppWidgetProviderInfo findWidgetProvider(boolean hasConfigureScreen) {
+ LauncherAppWidgetProviderInfo info = getOnUiThread(() -> {
+ Instrumentation i = getInstrumentation();
+ ComponentName cn = new ComponentName(i.getContext(),
+ hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
+ Log.d(TAG, "findWidgetProvider componentName=" + cn.flattenToString());
+ return new WidgetManagerHelper(i.getTargetContext()).findProvider(cn, myUserHandle());
+ });
if (info == null) {
throw new IllegalArgumentException("No valid widget provider");
}
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 026766c..5b9adcd 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
@@ -39,7 +38,6 @@
import com.android.launcher3.allapps.WorkPausedCard;
import com.android.launcher3.allapps.WorkProfileManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.rule.TestStabilityRule.Stability;
@@ -78,8 +76,6 @@
installDummyAppForUser(mProfileUserId);
updateWorkProfileSetupSuccessful("am start-user", output);
- Log.d(WORK_TAB_MISSING, "workProfileSuccessful? " + mWorkProfileSetupSuccessful +
- " shellCmd: " + logStr);
if (!mWorkProfileSetupSuccessful) {
return; // no need to setup launcher since all tests will skip.
}
@@ -89,14 +85,13 @@
waitForStateTransitionToEnd("Launcher internal state didn't switch to Normal",
() -> NORMAL);
waitForResumed("Launcher internal state is still Background");
- executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+ mLauncher.getWorkspace().switchToAllApps();
waitForStateTransitionToEnd("Launcher internal state didn't switch to All Apps",
() -> ALL_APPS);
}
@After
public void removeWorkProfile() throws Exception {
- Log.d(TestProtocol.WORK_TAB_MISSING, "WorkProfileTest teardown");
executeOnLauncher(launcher -> {
if (launcher == null || launcher.getAppsView() == null) {
return;
@@ -112,7 +107,6 @@
mLauncher.getAllApps();
waitForLauncherCondition("Work tab not setup", launcher -> {
if (launcher.getAppsView().getContentView() instanceof AllAppsPagedView) {
- Log.d(WORK_TAB_MISSING, "Deferring AppsStore updates");
launcher.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
return true;
}
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index e9a2b0f..b2ce400 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -30,10 +30,13 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
@@ -63,7 +66,7 @@
@Before
public void setUp() throws Exception {
super.setUp();
- mWidgetInfo = TestViewHelpers.findWidgetProvider(this, true /* hasConfigureScreen */);
+ mWidgetInfo = TestViewHelpers.findWidgetProvider(true /* hasConfigureScreen */);
mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext);
}
@@ -84,8 +87,7 @@
* @param acceptConfig accept the config activity
*/
private void runTest(boolean acceptConfig) throws Throwable {
- clearHomescreen();
- mDevice.pressHome();
+ new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
// Drag widget to homescreen
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
@@ -123,7 +125,10 @@
@Override
public boolean isTrue() throws Throwable {
- return mMainThreadExecutor.submit(mActivityMonitor.itemExists(this)).get();
+ return mMainThreadExecutor.submit(() -> {
+ Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+ return l != null && l.getWorkspace().getFirstMatch(this) != null;
+ }).get();
}
@Override
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 3eb20e3..9dca24b 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -18,17 +18,18 @@
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.PlatinumTest;
+import android.platform.test.rule.ScreenRecordRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.tapl.WidgetResizeFrame;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -51,14 +52,15 @@
@PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/289161193
public void testDragIcon() throws Throwable {
- clearHomescreen();
- mDevice.pressHome();
+ mLauncher.enableDebugTracing(); // b/289161193
+ new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
final LauncherAppWidgetProviderInfo widgetInfo =
- TestViewHelpers.findWidgetProvider(this, false /* hasConfigureScreen */);
+ TestViewHelpers.findWidgetProvider(false /* hasConfigureScreen */);
WidgetResizeFrame resizeFrame = mLauncher
.getWorkspace()
@@ -66,11 +68,6 @@
.getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager()))
.dragWidgetToWorkspace();
- assertTrue(mActivityMonitor.itemExists(
- (info, view) -> info instanceof LauncherAppWidgetInfo &&
- ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
- widgetInfo.provider.getClassName())).call());
-
assertNotNull("Widget resize frame not shown after widget add", resizeFrame);
resizeFrame.dismiss();
@@ -78,6 +75,7 @@
DEFAULT_UI_TIMEOUT);
assertNotNull("Widget not found on the workspace", widget);
widget.launch(getAppPackageName());
+ mLauncher.disableDebugTracing(); // b/289161193
}
/**
@@ -90,8 +88,8 @@
@Test
@PortraitLandscape
public void testDragCustomShortcut() throws Throwable {
- clearHomescreen();
- mDevice.pressHome();
+ new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+
mLauncher.getWorkspace().openAllWidgets()
.getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
.dragToWorkspace(false, true);
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 0f861eb..7db3161 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,8 +15,13 @@
*/
package com.android.launcher3.ui.widget;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
import static org.junit.Assert.assertEquals;
@@ -26,7 +31,6 @@
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
@@ -34,11 +38,14 @@
import android.os.Bundle;
import android.widget.RemoteViews;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.tapl.Widget;
@@ -57,6 +64,7 @@
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Tests for bind widget flow.
@@ -70,24 +78,18 @@
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
- private ContentResolver mResolver;
-
// Objects created during test, which should be cleaned up in the end.
private Cursor mCursor;
// App install session id.
private int mSessionId = -1;
+ private LauncherModel mModel;
+
@Override
@Before
public void setUp() throws Exception {
super.setUp();
-
- mResolver = mTargetContext.getContentResolver();
-
- // Clear all existing data
- LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- LauncherSettings.Settings.call(mResolver,
- LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+ mModel = LauncherAppState.getInstance(mTargetContext).getModel();
}
@After
@@ -103,34 +105,24 @@
@Test
public void testBindNormalWidget_withConfig() {
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
- LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
-
- addItemToScreen(item);
+ LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, true, i -> { });
verifyWidgetPresent(info);
}
@Test
public void testBindNormalWidget_withoutConfig() {
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
-
- addItemToScreen(item);
+ LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, true, i -> { });
verifyWidgetPresent(info);
}
@Test
public void testUnboundWidget_removed() {
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
- item.appWidgetId = -33;
-
- addItemToScreen(item);
+ LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
+ item -> item.appWidgetId = -33);
final Workspace workspace = mLauncher.getWorkspace();
// Item deleted from db
- mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
- null, null, null, null, null);
+ mCursor = queryItem();
assertEquals(0, mCursor.getCount());
// The view does not exist
@@ -140,36 +132,26 @@
@Test
public void testPendingWidget_autoRestored() {
// A non-restored widget with no config screen gets restored automatically.
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
-
// Do not bind the widget
- LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
- item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
-
- addItemToScreen(item);
+ LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
+ item -> item.restoreStatus = FLAG_ID_NOT_VALID);
verifyWidgetPresent(info);
}
@Test
public void testPendingWidget_withConfigScreen() {
// A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
- LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
-
// Do not bind the widget
- LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
- item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
-
- addItemToScreen(item);
+ LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, false,
+ item -> item.restoreStatus = FLAG_ID_NOT_VALID);
verifyPendingWidgetPresent();
- // Item deleted from db
- mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
- null, null, null, null, null);
+ mCursor = queryItem();
mCursor.moveToNext();
// Widget has a valid Id now.
assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
- & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+ & FLAG_ID_NOT_VALID);
assertNotNull(AppWidgetManager.getInstance(mTargetContext)
.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
LauncherSettings.Favorites.APPWIDGET_ID))));
@@ -185,7 +167,6 @@
appWidgetManager.updateAppWidgetOptions(appWidgetId, b);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
-
// verify changes are reflected
waitForLauncherCondition("App widget options did not update",
l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean(
@@ -197,17 +178,12 @@
@Test
public void testPendingWidget_notRestored_removed() {
- LauncherAppWidgetInfo item = getInvalidWidgetInfo();
- item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
- | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
-
- addItemToScreen(item);
+ addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
assertTrue("Pending widget exists",
mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
// Item deleted from db
- mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
- null, null, null, null, null);
+ mCursor = queryItem();
assertEquals(0, mCursor.getCount());
}
@@ -215,32 +191,25 @@
public void testPendingWidget_notRestored_brokenInstall() {
// A widget which is was being installed once, even if its not being
// installed at the moment is not removed.
- LauncherAppWidgetInfo item = getInvalidWidgetInfo();
- item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
- | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
- | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
-
- addItemToScreen(item);
+ addPendingItemToScreen(getInvalidWidgetInfo(),
+ FLAG_ID_NOT_VALID | FLAG_RESTORE_STARTED | FLAG_PROVIDER_NOT_READY);
verifyPendingWidgetPresent();
// Verify item still exists in db
- mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
- null, null, null, null, null);
+ mCursor = queryItem();
assertEquals(1, mCursor.getCount());
// Widget still has an invalid id.
mCursor.moveToNext();
- assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
+ assertEquals(FLAG_ID_NOT_VALID,
mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
- & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+ & FLAG_ID_NOT_VALID);
}
@Test
public void testPendingWidget_notRestored_activeInstall() throws Exception {
// A widget which is being installed is not removed
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
- item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
- | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
// Create an active installer session
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
@@ -248,19 +217,18 @@
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
mSessionId = installer.createSession(params);
- addItemToScreen(item);
+ addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
verifyPendingWidgetPresent();
// Verify item still exists in db
- mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
- null, null, null, null, null);
+ mCursor = queryItem();
assertEquals(1, mCursor.getCount());
// Widget still has an invalid id.
mCursor.moveToNext();
- assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
+ assertEquals(FLAG_ID_NOT_VALID,
mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
- & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+ & FLAG_ID_NOT_VALID);
}
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
@@ -275,6 +243,28 @@
widget != null);
}
+ private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) {
+ item.restoreStatus = restoreStatus;
+ item.screenId = FIRST_SCREEN_ID;
+ new FavoriteItemsTransaction(mTargetContext)
+ .addItem(() -> item)
+ .commitAndLoadHome(mLauncher);
+ }
+
+ private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen,
+ boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) {
+ LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen);
+ new FavoriteItemsTransaction(mTargetContext)
+ .addItem(() -> {
+ LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget);
+ item.screenId = FIRST_SCREEN_ID;
+ itemOverride.accept(item);
+ return item;
+ })
+ .commitAndLoadHome(mLauncher);
+ return info;
+ }
+
/**
* Returns a LauncherAppWidgetInfo with package name which is not present on the device
*/
@@ -312,4 +302,14 @@
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
return item;
}
+
+ private Cursor queryItem() {
+ try {
+ return MODEL_EXECUTOR.submit(() ->
+ mModel.getModelDbController().query(
+ TABLE_NAME, null, itemIdMatch(0), null, null)).get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index bf9eb5a..a6b5369 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -32,7 +32,9 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -134,8 +136,7 @@
private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
Intent... commandIntents) throws Throwable {
- clearHomescreen();
- mDevice.pressHome();
+ new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
// Open Pin item activity
BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
@@ -193,7 +194,10 @@
@Override
public boolean isTrue() throws Throwable {
- return mMainThreadExecutor.submit(mActivityMonitor.itemExists(mOp)).get();
+ return mMainThreadExecutor.submit(() -> {
+ Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+ return l != null && l.getWorkspace().getFirstMatch(mOp) != null;
+ }).get();
}
}
}
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index 7ba0b53..8e5e9cc 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -147,9 +147,8 @@
for (int i = parent.getChildCount() - 1; i >= 0; i--) {
viewQueue.add(parent.getChildAt(i));
}
- } else if (view instanceof BubbleTextView) {
- BubbleTextView btv = (BubbleTextView) view;
- if (title.equals(btv.getText())) {
+ } else if (view instanceof BubbleTextView btv) {
+ if (title.equals(btv.getContentDescription().toString())) {
icon = btv;
break;
}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index c4b6d43..62a8179 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -29,6 +29,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
@@ -177,14 +178,15 @@
public void testDragIconToPage3() {
Workspace workspace = mLauncher.getWorkspace();
- workspace.dragIcon(workspace.getHotseatAppIcon("Phone"), 3);
+ // b/299522368 sometimes the phone app is not present in the hotseat.
+ workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 3);
executeOnLauncher(launcher -> {
assertPagesExist(launcher, 0, 1, 2, 3);
assertItemsOnPage(launcher, 0, "Play Store", "Maps");
assertPageEmpty(launcher, 1);
assertPageEmpty(launcher, 2);
- assertItemsOnPage(launcher, 3, "Phone");
+ assertItemsOnPage(launcher, 3, "Chrome");
});
}
diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
new file mode 100644
index 0000000..8e4e998
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.Configuration
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.Rect
+import android.hardware.display.DisplayManager
+import android.util.ArrayMap
+import android.util.DisplayMetrics
+import android.view.Display
+import android.view.Surface
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
+import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.window.CachedDisplayInfo
+import com.android.launcher3.util.window.WindowManagerProxy
+import kotlin.math.min
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+import org.mockito.stubbing.Answer
+
+/** Unit tests for {@link DisplayController} */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DisplayControllerTest {
+
+ private val appContext: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock private lateinit var context: SandboxContext
+ @Mock private lateinit var windowManagerProxy: WindowManagerProxy
+ @Mock private lateinit var launcherPrefs: LauncherPrefs
+ @Mock private lateinit var displayManager: DisplayManager
+ @Mock private lateinit var display: Display
+ @Mock private lateinit var resources: Resources
+ @Mock private lateinit var displayInfoChangeListener: DisplayInfoChangeListener
+
+ private lateinit var displayController: DisplayController
+
+ private val width = 2208
+ private val height = 1840
+ private val inset = 110
+ private val densityDpi = 420
+ private val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ private val bounds =
+ arrayOf(
+ WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_0),
+ WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_90),
+ WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_180),
+ WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270)
+ )
+ private val configuration =
+ Configuration(appContext.resources.configuration).apply {
+ densityDpi = this@DisplayControllerTest.densityDpi
+ screenWidthDp = (bounds[0].bounds.width() / density).toInt()
+ screenHeightDp = (bounds[0].bounds.height() / density).toInt()
+ smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
+ whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
+
+ // Mock WindowManagerProxy
+ val displayInfo =
+ CachedDisplayInfo(Point(width, height), Surface.ROTATION_0, Rect(0, 0, 0, 0))
+ whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
+ whenever(windowManagerProxy.estimateInternalDisplayBounds(any()))
+ .thenAnswer(
+ Answer {
+ // Always create a new copy of bounds
+ val perDisplayBounds = ArrayMap<CachedDisplayInfo, List<WindowBounds>>()
+ perDisplayBounds[displayInfo] = bounds.toList()
+ return@Answer perDisplayBounds
+ }
+ )
+ whenever(windowManagerProxy.getRealBounds(any(), any())).thenAnswer { i ->
+ bounds[i.getArgument<CachedDisplayInfo>(1).rotation]
+ }
+
+ // Mock context
+ whenever(context.createWindowContext(any(), any(), nullable())).thenReturn(context)
+ whenever(context.getSystemService(eq(DisplayManager::class.java)))
+ .thenReturn(displayManager)
+ doNothing().`when`(context).registerComponentCallbacks(any())
+
+ // Mock display
+ whenever(display.rotation).thenReturn(displayInfo.rotation)
+ whenever(context.display).thenReturn(display)
+ whenever(displayManager.getDisplay(any())).thenReturn(display)
+
+ // Mock resources
+ whenever(resources.configuration).thenReturn(configuration)
+ whenever(context.resources).thenReturn(resources)
+
+ // Initialize DisplayController
+ displayController = DisplayController(context)
+ displayController.addChangeListener(displayInfoChangeListener)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testRotation() {
+ val displayInfo =
+ CachedDisplayInfo(Point(height, width), Surface.ROTATION_90, Rect(0, 0, 0, 0))
+ whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
+ whenever(display.rotation).thenReturn(displayInfo.rotation)
+ val configuration =
+ Configuration(configuration).apply {
+ screenWidthDp = configuration.screenHeightDp
+ screenHeightDp = configuration.screenWidthDp
+ }
+ whenever(resources.configuration).thenReturn(configuration)
+
+ displayController.onConfigurationChanged(configuration)
+
+ verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_ROTATION))
+ }
+
+ @Test
+ @UiThreadTest
+ fun testFontScale() {
+ val configuration = Configuration(configuration).apply { fontScale = 1.2f }
+ whenever(resources.configuration).thenReturn(configuration)
+
+ displayController.onConfigurationChanged(configuration)
+
+ verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_DENSITY))
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt b/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt
new file mode 100644
index 0000000..d9e3377
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.Configuration
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconSizeStepsTest {
+ private var context: Context? = null
+ private val runningContext: Context = ApplicationProvider.getApplicationContext()
+ private lateinit var iconSizeSteps: IconSizeSteps
+
+ @Before
+ fun setup() {
+ // 160dp makes 1px = 1dp
+ val config =
+ Configuration(runningContext.resources.configuration).apply { this.densityDpi = 160 }
+ context = runningContext.createConfigurationContext(config)
+ iconSizeSteps = IconSizeSteps(context!!.resources)
+ }
+
+ @Test
+ fun minimumIconSize() {
+ assertThat(iconSizeSteps.minimumIconSize()).isEqualTo(52)
+ }
+
+ @Test
+ fun getNextLowerIconSize() {
+ assertThat(iconSizeSteps.getNextLowerIconSize(66)).isEqualTo(63)
+
+ // Assert that never goes below minimum
+ assertThat(iconSizeSteps.getNextLowerIconSize(52)).isEqualTo(52)
+ assertThat(iconSizeSteps.getNextLowerIconSize(30)).isEqualTo(52)
+ }
+
+ @Test
+ fun getIconSmallerThan() {
+ assertThat(iconSizeSteps.getIconSmallerThan(60)).isEqualTo(59)
+
+ // Assert that never goes below minimum
+ assertThat(iconSizeSteps.getIconSmallerThan(52)).isEqualTo(52)
+ assertThat(iconSizeSteps.getIconSmallerThan(30)).isEqualTo(52)
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 976afcd..261436b 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -15,34 +15,31 @@
*/
package com.android.launcher3.util;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
-import android.content.res.Resources;
-import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-import android.os.Process;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.util.ArrayMap;
@@ -56,14 +53,10 @@
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.testing.TestInformationProvider;
@@ -72,37 +65,25 @@
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.widget.custom.CustomWidgetManager;
-import org.mockito.ArgumentCaptor;
-
-import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.InputStreamReader;
+import java.io.IOException;
import java.io.OutputStreamWriter;
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.function.Function;
/**
* Utility class to help manage Launcher Model and related objects for test.
*/
public class LauncherModelHelper {
- public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
- public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
- public static final int NO__ICON = -1;
-
- public static final String TEST_PACKAGE = testContext().getPackageName();
+ public static final String TEST_PACKAGE = getInstrumentation().getContext().getPackageName();
public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2";
+ public static final String TEST_ACTIVITY2 = "com.android.launcher3.tests.Activity3";
+ public static final String TEST_ACTIVITY3 = "com.android.launcher3.tests.Activity4";
// Authority for providing a test default-workspace-layout data.
private static final String TEST_PROVIDER_AUTHORITY =
@@ -110,39 +91,18 @@
private static final int DEFAULT_BITMAP_SIZE = 10;
private static final int DEFAULT_GRID_SIZE = 4;
- private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>();
- private final MockContentResolver mMockResolver = new MockContentResolver();
- public final TestLauncherProvider provider;
public final SandboxModelContext sandboxContext;
- public final long defaultProfileId;
+ private final RunnableList mDestroyTask = new RunnableList();
private BgDataModel mDataModel;
- private AllAppsList mAllAppsList;
public LauncherModelHelper() {
- Context context = getApplicationContext();
- // System settings cache content provider. Ensure that they are statically initialized
- Settings.Secure.getString(context.getContentResolver(), "test");
- Settings.System.getString(context.getContentResolver(), "test");
- Settings.Global.getString(context.getContentResolver(), "test");
-
- provider = new TestLauncherProvider();
sandboxContext = new SandboxModelContext();
- defaultProfileId = UserCache.INSTANCE.get(sandboxContext)
- .getSerialNumberForUser(Process.myUserHandle());
- setupProvider(LauncherProvider.AUTHORITY, provider);
}
public void setupProvider(String authority, ContentProvider provider) {
- ProviderInfo providerInfo = new ProviderInfo();
- providerInfo.authority = authority;
- providerInfo.applicationInfo = sandboxContext.getApplicationInfo();
- provider.attachInfo(sandboxContext, providerInfo);
- mMockResolver.addProvider(providerInfo.authority, provider);
- doReturn(providerInfo)
- .when(sandboxContext.mPm)
- .resolveContentProvider(eq(authority), anyInt());
+ sandboxContext.setupProvider(authority, provider);
}
public LauncherModel getModel() {
@@ -151,16 +111,35 @@
public synchronized BgDataModel getBgDataModel() {
if (mDataModel == null) {
- mDataModel = ReflectionHelpers.getField(getModel(), "mBgDataModel");
+ getModel().enqueueModelUpdateTask(new ModelUpdateTask() {
+ @Override
+ public void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
+ @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
+ @NonNull Executor uiExecutor) {
+ mDataModel = dataModel;
+ }
+
+ @Override
+ public void run() { }
+ });
}
return mDataModel;
}
- public synchronized AllAppsList getAllAppsList() {
- if (mAllAppsList == null) {
- mAllAppsList = ReflectionHelpers.getField(getModel(), "mBgAllAppsList");
- }
- return mAllAppsList;
+ /**
+ * Creates a installer session for the provided package.
+ */
+ public int createInstallerSession(String pkg) throws IOException {
+ SessionParams sp = new SessionParams(MODE_FULL_INSTALL);
+ sp.setAppPackageName(pkg);
+ Bitmap icon = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+ icon.eraseColor(Color.RED);
+ sp.setAppIcon(icon);
+ sp.setAppLabel(pkg);
+ PackageInstaller pi = sandboxContext.getPackageManager().getPackageInstaller();
+ int sessionId = pi.createSession(sp);
+ mDestroyTask.add(() -> pi.abandonSession(sessionId));
+ return sessionId;
}
public void destroy() {
@@ -175,6 +154,8 @@
waitOrThrow(l1);
sandboxContext.onDestroy();
l2.countDown();
+
+ mDestroyTask.executeAllAndDestroy();
}
private void waitOrThrow(CountDownLatch latch) {
@@ -186,184 +167,6 @@
}
/**
- * Synchronously executes the task and returns all the UI callbacks posted.
- */
- public List<Runnable> executeTaskForTest(ModelUpdateTask task) throws Exception {
- LauncherModel model = getModel();
- if (!model.isModelLoaded()) {
- ReflectionHelpers.setField(model, "mModelLoaded", true);
- }
- Executor mockExecutor = mock(Executor.class);
- model.enqueueModelUpdateTask(new ModelUpdateTask() {
- @Override
- public void init(@NonNull final LauncherAppState app,
- @NonNull final LauncherModel model, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList allAppsList, @NonNull final Executor uiExecutor) {
- task.init(app, model, dataModel, allAppsList, mockExecutor);
- }
-
- @Override
- public void run() {
- task.run();
- }
- });
- MODEL_EXECUTOR.submit(() -> null).get();
-
- ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
- verify(mockExecutor, atLeast(0)).execute(captor.capture());
- return captor.getAllValues();
- }
-
- /**
- * Synchronously executes a task on the model
- */
- public <T> T executeSimpleTask(Function<BgDataModel, T> task) throws Exception {
- BgDataModel dataModel = getBgDataModel();
- return MODEL_EXECUTOR.submit(() -> task.apply(dataModel)).get();
- }
-
- /**
- * Initializes mock data for the test.
- */
- public void initializeData(String resourceName) throws Exception {
- BgDataModel bgDataModel = getBgDataModel();
- AllAppsList allAppsList = getAllAppsList();
-
- MODEL_EXECUTOR.submit(() -> {
- // Copy apk from resources to a local file and install from there.
- Resources resources = testContext().getResources();
- int resId = resources.getIdentifier(
- resourceName, "raw", testContext().getPackageName());
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(
- resources.openRawResource(resId)))) {
- String line;
- HashMap<String, Class> classMap = new HashMap<>();
- while ((line = reader.readLine()) != null) {
- line = line.trim();
- if (line.startsWith("#") || line.isEmpty()) {
- continue;
- }
- String[] commands = line.split(" ");
- switch (commands[0]) {
- case "classMap":
- classMap.put(commands[1], Class.forName(commands[2]));
- break;
- case "bgItem":
- bgDataModel.addItem(sandboxContext,
- (ItemInfo) initItem(classMap.get(commands[1]), commands, 2),
- false);
- break;
- case "allApps":
- allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
- break;
- }
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }).get();
- }
-
- private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
- HashMap<String, Field> cache = mFieldCache.get(clazz);
- if (cache == null) {
- cache = new HashMap<>();
- Class c = clazz;
- while (c != null) {
- for (Field f : c.getDeclaredFields()) {
- f.setAccessible(true);
- cache.put(f.getName(), f);
- }
- c = c.getSuperclass();
- }
- mFieldCache.put(clazz, cache);
- }
-
- Object item = clazz.newInstance();
- for (int i = startIndex; i < fieldDef.length; i++) {
- String[] fieldData = fieldDef[i].split("=", 2);
- Field f = cache.get(fieldData[0]);
- Class type = f.getType();
- if (type == int.class || type == long.class) {
- f.set(item, Integer.parseInt(fieldData[1]));
- } else if (type == CharSequence.class || type == String.class) {
- f.set(item, fieldData[1]);
- } else if (type == Intent.class) {
- if (!fieldData[1].startsWith("#Intent")) {
- fieldData[1] = "#Intent;" + fieldData[1] + ";end";
- }
- f.set(item, Intent.parseUri(fieldData[1], 0));
- } else if (type == ComponentName.class) {
- f.set(item, ComponentName.unflattenFromString(fieldData[1]));
- } else {
- throw new Exception("Added parsing logic for "
- + f.getName() + " of type " + f.getType());
- }
- }
- return item;
- }
-
- public int addItem(int type, int screen, int container, int x, int y) {
- return addItem(type, screen, container, x, y, defaultProfileId, TEST_PACKAGE);
- }
-
- public int addItem(int type, int screen, int container, int x, int y, long profileId) {
- return addItem(type, screen, container, x, y, profileId, TEST_PACKAGE);
- }
-
- public int addItem(int type, int screen, int container, int x, int y, String packageName) {
- return addItem(type, screen, container, x, y, defaultProfileId, packageName);
- }
-
- public int addItem(int type, int screen, int container, int x, int y, String packageName,
- int id, Uri contentUri) {
- addItem(type, screen, container, x, y, defaultProfileId, packageName, id, contentUri);
- return id;
- }
-
- /**
- * Adds a mock item in the DB.
- * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
- * folder (where the type represents the number of items in the folder).
- */
- public int addItem(int type, int screen, int container, int x, int y, long profileId,
- String packageName) {
- int id = LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
- addItem(type, screen, container, x, y, profileId, packageName, id, CONTENT_URI);
- return id;
- }
-
- public void addItem(int type, int screen, int container, int x, int y, long profileId,
- String packageName, int id, Uri contentUri) {
- ContentValues values = new ContentValues();
- values.put(LauncherSettings.Favorites._ID, id);
- values.put(LauncherSettings.Favorites.CONTAINER, container);
- values.put(LauncherSettings.Favorites.SCREEN, screen);
- values.put(LauncherSettings.Favorites.CELLX, x);
- values.put(LauncherSettings.Favorites.CELLY, y);
- values.put(LauncherSettings.Favorites.SPANX, 1);
- values.put(LauncherSettings.Favorites.SPANY, 1);
- values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
-
- if (type == APP_ICON || type == SHORTCUT) {
- values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
- values.put(LauncherSettings.Favorites.INTENT,
- new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
- } else {
- values.put(LauncherSettings.Favorites.ITEM_TYPE,
- LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
- // Add folder items.
- for (int i = 0; i < type; i++) {
- addItem(APP_ICON, 0, id, 0, 0, profileId);
- }
- }
-
- sandboxContext.getContentResolver().insert(contentUri, values);
- }
-
- /**
* Sets up a mock provider to load the provided layout by default, next time the layout loads
*/
public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
@@ -394,6 +197,9 @@
}
};
setupProvider(TEST_PROVIDER_AUTHORITY, cp);
+ mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () ->
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+ "settings delete secure launcher3.layout.provider")));
return this;
}
@@ -402,46 +208,16 @@
*/
public void loadModelSync() throws ExecutionException, InterruptedException {
Callbacks mockCb = new Callbacks() { };
- Executors.MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
+ MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
- Executors.MAIN_EXECUTOR.submit(() -> { }).get();
- Executors.MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
+ MAIN_EXECUTOR.submit(() -> { }).get();
+ MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
}
- /**
- * An extension of LauncherProvider backed up by in-memory database.
- */
- public static class TestLauncherProvider extends LauncherProvider {
+ public static class SandboxModelContext extends SandboxContext {
- @Override
- public boolean onCreate() {
- return true;
- }
-
- public SQLiteDatabase getDb() {
- return getModelDbController().getDb();
- }
- }
-
- public static boolean deleteContents(File dir) {
- File[] files = dir.listFiles();
- boolean success = true;
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- success &= deleteContents(file);
- }
- if (!file.delete()) {
- success = false;
- }
- }
- }
- return success;
- }
-
- public class SandboxModelContext extends SandboxContext {
-
+ private final MockContentResolver mMockResolver = new MockContentResolver();
private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
private final PackageManager mPm;
private final File mDbDir;
@@ -451,12 +227,30 @@
UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
- SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE,
+ SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
+ LockedUserState.INSTANCE, WallpaperColorHints.INSTANCE,
ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
+
+ // System settings cache content provider. Ensure that they are statically initialized
+ Settings.Secure.getString(
+ ApplicationProvider.getApplicationContext().getContentResolver(), "test");
+ Settings.System.getString(
+ ApplicationProvider.getApplicationContext().getContentResolver(), "test");
+ Settings.Global.getString(
+ ApplicationProvider.getApplicationContext().getContentResolver(), "test");
+
mPm = spy(getBaseContext().getPackageManager());
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
}
+ @Override
+ protected <T> T createObject(MainThreadInitializedObject<T> object) {
+ if (object == LauncherAppState.INSTANCE) {
+ return (T) new LauncherAppState(this, null /* iconCacheFileName */);
+ }
+ return super.createObject(object);
+ }
+
public SandboxModelContext allow(MainThreadInitializedObject object) {
mAllowedObjects.add(object);
return this;
@@ -505,9 +299,30 @@
mSpiedServices.put(name, result);
return result;
}
- }
- private static Context testContext() {
- return getInstrumentation().getContext();
+ public void setupProvider(String authority, ContentProvider provider) {
+ ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.authority = authority;
+ providerInfo.applicationInfo = getApplicationInfo();
+ provider.attachInfo(this, providerInfo);
+ mMockResolver.addProvider(providerInfo.authority, provider);
+ doReturn(providerInfo).when(mPm).resolveContentProvider(eq(authority), anyInt());
+ }
+
+ private static boolean deleteContents(File dir) {
+ File[] files = dir.listFiles();
+ boolean success = true;
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ success &= deleteContents(file);
+ }
+ if (!file.delete()) {
+ success = false;
+ }
+ }
+ }
+ return success;
+ }
}
}
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 84156e7..92ab2cb 100644
--- a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -32,7 +32,7 @@
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-/** Unit tests for {@link LockedUserUtil} */
+/** Unit tests for {@link LockedUserState} */
@SmallTest
@RunWith(AndroidJUnit4::class)
class LockedUserStateTest {
@@ -49,40 +49,31 @@
@Test
fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
`when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
- LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
val action: Runnable = mock()
-
- LockedUserState.get(context).runOnUserUnlocked(action)
+ LockedUserState(context).runOnUserUnlocked(action)
verify(action).run()
}
@Test
fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
`when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
- LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
val action: Runnable = mock()
-
- LockedUserState.get(context).runOnUserUnlocked(action)
+ val state = LockedUserState(context)
+ state.runOnUserUnlocked(action)
verifyZeroInteractions(action)
-
- LockedUserState.get(context)
- .mUserUnlockedReceiver
- .onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
-
+ state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
verify(action).run()
}
@Test
fun isUserUnlocked_returns_true_when_user_is_unlocked() {
`when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
- LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
- assertThat(LockedUserState.get(context).isUserUnlocked).isTrue()
+ assertThat(LockedUserState(context).isUserUnlocked).isTrue()
}
@Test
fun isUserUnlocked_returns_false_when_user_is_locked() {
`when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
- LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
- assertThat(LockedUserState.get(context).isUserUnlocked).isFalse()
+ assertThat(LockedUserState(context).isUserUnlocked).isFalse()
}
}
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
deleted file mode 100644
index ed2ec7b..0000000
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ /dev/null
@@ -1,500 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import static com.android.launcher3.util.Executors.createAndStartNewLooper;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Event processor for reliably reproducing multithreaded apps race conditions in tests.
- *
- * The app notifies us about “events” that happen in its threads. The race condition test runs the
- * test action multiple times (aka iterations), trying to generate all possible permutations of
- * these events. It keeps a set of all seen event sequences and steers the execution towards
- * executing events in previously unseen order. It does it by postponing execution of threads that
- * would lead to an already seen sequence.
- *
- * If an event A occurs before event B in the sequence, this is how execution order looks like:
- * Events: ... A ... B ...
- * Events and instructions, guaranteed order:
- * (instructions executed prior to A) A ... B (instructions executed after B)
- *
- * Each iteration has 3 parts (phases).
- * Phase 1. Picking a previously seen event subsequence that we believe can have previously unseen
- * continuations. Reproducing this sequence by pausing threads that would lead to other sequences.
- * Phase 2. Trying to generate previously unseen continuation of the sequence from Phase 1. We need
- * one new event after that sequence. All threads leading to seen continuations will be postponed
- * for some short period of time. The phase ends once the new event is registered, or after the
- * period of time ends (in which case we declare that the sequence can’t have new continuations).
- * Phase 3. Releasing all threads and letting the test iteration run till its end.
- *
- * The iterations end when all seen paths have been declared “uncontinuable”.
- *
- * When we register event XXX:enter, we hold all other events until we register XXX:exit.
- */
-public class RaceConditionReproducer {
- private static final String TAG = "RaceConditionReproducer";
-
- private static final boolean ENTER = true;
- private static final boolean EXIT = false;
- private static final String ENTER_POSTFIX = "enter";
- private static final String EXIT_POSTFIX = "exit";
-
- private static final long SHORT_TIMEOUT_MS = 2000;
- private static final long LONG_TIMEOUT_MS = 60000;
- // Handler used to resume postponed events.
- private static final Handler POSTPONED_EVENT_RESUME_HANDLER =
- new Handler(createAndStartNewLooper("RaceConditionEventResumer"));
-
- public static String enterExitEvt(String eventName, boolean isEnter) {
- return eventName + ":" + (isEnter ? ENTER_POSTFIX : EXIT_POSTFIX);
- }
-
- public static String enterEvt(String eventName) {
- return enterExitEvt(eventName, ENTER);
- }
-
- public static String exitEvt(String eventName) {
- return enterExitEvt(eventName, EXIT);
- }
-
- /**
- * Event in a particular sequence of events. A node in the prefix tree of all seen event
- * sequences.
- */
- private class EventNode {
- // Events that were seen just after this event.
- private final Map<String, EventNode> mNextEvents = new HashMap<>();
- // Whether we believe that further iterations will not be able to add more events to
- // mNextEvents.
- private boolean mStoppedAddingChildren = true;
-
- private void debugDump(StringBuilder sb, int indent, String name) {
- for (int i = 0; i < indent; ++i) sb.append('.');
- sb.append(!mStoppedAddingChildren ? "+" : "-");
- sb.append(" : ");
- sb.append(name);
- if (mLastRegisteredEvent == this) sb.append(" <");
- sb.append('\n');
-
- for (String key : mNextEvents.keySet()) {
- mNextEvents.get(key).debugDump(sb, indent + 2, key);
- }
- }
-
- /** Number of leaves in the subtree with this node as a root. */
- private int numberOfLeafNodes() {
- if (mNextEvents.isEmpty()) return 1;
-
- int leaves = 0;
- for (String event : mNextEvents.keySet()) {
- leaves += mNextEvents.get(event).numberOfLeafNodes();
- }
- return leaves;
- }
-
- /**
- * Whether we believe that further iterations will not be able add nodes to the subtree with
- * this node as a root.
- */
- private boolean stoppedAddingChildrenToTree() {
- if (!mStoppedAddingChildren) return false;
-
- for (String event : mNextEvents.keySet()) {
- if (!mNextEvents.get(event).stoppedAddingChildrenToTree()) return false;
- }
- return true;
- }
-
- /**
- * In the subtree with this node as a root, tries finding a node where we may have a
- * chance to add new children.
- * If succeeds, returns true and fills 'path' with the sequence of events to that node;
- * otherwise returns false.
- */
- private boolean populatePathToGrowthPoint(List<String> path) {
- for (String event : mNextEvents.keySet()) {
- if (mNextEvents.get(event).populatePathToGrowthPoint(path)) {
- path.add(0, event);
- return true;
- }
- }
- if (!mStoppedAddingChildren) {
- // Mark that we have finished adding children. It will remain true if no new
- // children are added, or will be set to false upon adding a new child.
- mStoppedAddingChildren = true;
- return true;
- }
- return false;
- }
- }
-
- // Starting point of all event sequences; the root of the prefix tree representation all
- // sequences generated by test iterations. A test iteration can add nodes int it.
- private EventNode mRoot = new EventNode();
- // During a test iteration, the last event that was registered.
- private EventNode mLastRegisteredEvent;
- // Length of the current sequence of registered events for the current test iteration.
- private int mRegisteredEventCount = 0;
- // During the first part of a test iteration, we go to a specific node under mRoot by
- // 'playing back' mSequenceToFollow. During this part, all events that don't belong to this
- // sequence get postponed.
- private List<String> mSequenceToFollow = new ArrayList<>();
- // Collection of events that got postponed, with corresponding wait objects used to let them go.
- private Map<String, Semaphore> mPostponedEvents = new HashMap<>();
- // Callback to run by POSTPONED_EVENT_RESUME_HANDLER, used to let go of all currently
- // postponed events.
- private Runnable mResumeAllEventsCallback;
- // String representation of the sequence of events registered so far for the current test
- // iteration. After registering any event, we output it to the log. The last output before
- // the test failure can be later played back to reliable reproduce the exact sequence of
- // events that broke the test.
- // Format: EV1|EV2|...\EVN
- private StringBuilder mCurrentSequence;
- // When not null, we are in a repro mode. We run only one test iteration, and are trying to
- // reproduce the event sequence represented by this string. The format is same as for
- // mCurrentSequence.
- private final String mReproString;
-
- /* Constructor for a normal test. */
- public RaceConditionReproducer() {
- mReproString = null;
- }
-
- /**
- * Constructor for reliably reproducing a race condition failure. The developer should find in
- * the log the latest "Repro sequence:" record and locally modify the test by passing that
- * string to the constructor. Running the test will have only one iteration that will reliably
- * "play back" that sequence.
- */
- public RaceConditionReproducer(String reproString) {
- mReproString = reproString;
- }
-
- public RaceConditionReproducer(String... reproSequence) {
- this(String.join("|", reproSequence));
- }
-
- public synchronized String getCurrentSequenceString() {
- return mCurrentSequence.toString();
- }
-
- /**
- * Starts a new test iteration. Events reported via RaceConditionTracker.onEvent before this
- * call will be ignored.
- */
- public synchronized void startIteration() {
- mLastRegisteredEvent = mRoot;
- mRegisteredEventCount = 0;
- mCurrentSequence = new StringBuilder();
- Log.d(TAG, "Repro sequence: " + mCurrentSequence);
- mSequenceToFollow = mReproString != null ?
- parseReproString(mReproString) : generateSequenceToFollowLocked();
- Log.e(TAG, "---- Start of iteration; state:\n" + dumpStateLocked());
- checkIfCompletedSequenceToFollowLocked();
-
- TraceHelperForTest.setRaceConditionReproducer(this);
- }
-
- /**
- * Ends a new test iteration. Events reported via RaceConditionTracker.onEvent after this call
- * will be ignored.
- * Returns whether we need more iterations.
- */
- public synchronized boolean finishIteration() {
- TraceHelperForTest.setRaceConditionReproducer(null);
-
- runResumeAllEventsCallbackLocked();
- assertTrue("Non-empty postponed events", mPostponedEvents.isEmpty());
- assertTrue("Last registered event is :enter", lastEventAsEnter() == null);
-
- // No events came after mLastRegisteredEvent. It doesn't make sense to come to it again
- // because we won't see new continuations.
- mLastRegisteredEvent.mStoppedAddingChildren = true;
- Log.e(TAG, "---- End of iteration; state:\n" + dumpStateLocked());
- if (mReproString != null) {
- assertTrue("Repro mode: failed to reproduce the sequence",
- mCurrentSequence.toString().startsWith(mReproString));
- }
- // If we are in a repro mode, we need only one iteration. Otherwise, continue if the tree
- // has prospective growth points.
- return mReproString == null && !mRoot.stoppedAddingChildrenToTree();
- }
-
- private static List<String> parseReproString(String reproString) {
- return Arrays.asList(reproString.split("\\|"));
- }
-
- /**
- * Called when the app issues an event.
- */
- public void onEvent(String event) {
- final Semaphore waitObject = tryRegisterEvent(event);
- if (waitObject != null) {
- waitUntilCanRegister(event, waitObject);
- }
- }
-
- /**
- * Returns whether the last event was not an XXX:enter, or this event is a matching XXX:exit.
- */
- private boolean canRegisterEventNowLocked(String event) {
- final String lastEventAsEnter = lastEventAsEnter();
- final String thisEventAsExit = eventAsExit(event);
-
- if (lastEventAsEnter != null) {
- if (!lastEventAsEnter.equals(thisEventAsExit)) {
- assertTrue("YYY:exit after XXX:enter", thisEventAsExit == null);
- // Last event was :enter, but this event is not :exit.
- return false;
- }
- } else {
- // Previous event was not :enter.
- assertTrue(":exit after a non-enter event", thisEventAsExit == null);
- }
- return true;
- }
-
- /**
- * Registers an event issued by the app and returns null or decides that the event must be
- * postponed, and returns an object to wait on.
- */
- private synchronized Semaphore tryRegisterEvent(String event) {
- Log.d(TAG, "Event issued by the app: " + event);
-
- if (!canRegisterEventNowLocked(event)) {
- return createWaitObjectForPostponedEventLocked(event);
- }
-
- if (mRegisteredEventCount < mSequenceToFollow.size()) {
- // We are in the first part of the iteration. We only register events that follow the
- // mSequenceToFollow and postponing all other events.
- if (event.equals(mSequenceToFollow.get(mRegisteredEventCount))) {
- // The event is the next one expected in the sequence. Register it.
- registerEventLocked(event);
-
- // If there are postponed events that could continue the sequence, register them.
- while (mRegisteredEventCount < mSequenceToFollow.size() &&
- mPostponedEvents.containsKey(
- mSequenceToFollow.get(mRegisteredEventCount))) {
- registerPostponedEventLocked(mSequenceToFollow.get(mRegisteredEventCount));
- }
-
- // Perhaps we just completed the required sequence...
- checkIfCompletedSequenceToFollowLocked();
- } else {
- // The event is not the next one in the sequence. Postpone it.
- return createWaitObjectForPostponedEventLocked(event);
- }
- } else if (mRegisteredEventCount == mSequenceToFollow.size()) {
- // The second phase of the iteration. We have just registered the whole
- // mSequenceToFollow, and want to add previously not seen continuations for the last
- // node in the sequence aka 'growth point'.
- if (!mLastRegisteredEvent.mNextEvents.containsKey(event) || mReproString != null) {
- // The event was never seen as a continuation for the current node.
- // Or we are in repro mode, in which case we are not in business of generating
- // new sequences after we've played back the required sequence.
- // Register it immediately.
- registerEventLocked(event);
- } else {
- // The event was seen as a continuation for the current node. Postpone it, hoping
- // that a new event will come from other threads.
- return createWaitObjectForPostponedEventLocked(event);
- }
- } else {
- // The third phase of the iteration. We are past the growth point and register
- // everything that comes.
- registerEventLocked(event);
- // Register events that may have been postponed while waiting for an :exit event
- // during the third phase. We don't do this if just registered event is :enter.
- if (eventAsEnter(event) == null && mRegisteredEventCount > mSequenceToFollow.size()) {
- registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
- }
- }
- return null;
- }
-
- /** Called when there are chances that we just have registered the whole mSequenceToFollow. */
- private void checkIfCompletedSequenceToFollowLocked() {
- if (mRegisteredEventCount == mSequenceToFollow.size()) {
- // We just entered the second phase of the iteration. We have just registered the
- // whole mSequenceToFollow, and want to add previously not seen continuations for the
- // last node in the sequence aka 'growth point'. All seen continuations will be
- // postponed for SHORT_TIMEOUT_MS. At the end of this time period, we'll let them go.
- scheduleResumeAllEventsLocked();
-
- // Among the events that were postponed during the first stage, there may be an event
- // that wasn't seen after the current. If so, register it immediately because this
- // creates a new sequence.
- final Set<String> keys = new HashSet<>(mPostponedEvents.keySet());
- keys.removeAll(mLastRegisteredEvent.mNextEvents.keySet());
- if (!keys.isEmpty()) {
- registerPostponedEventLocked(keys.iterator().next());
- }
- }
- }
-
- private Semaphore createWaitObjectForPostponedEventLocked(String event) {
- final Semaphore waitObject = new Semaphore(0);
- assertTrue("Event already postponed: " + event, !mPostponedEvents.containsKey(event));
- mPostponedEvents.put(event, waitObject);
- return waitObject;
- }
-
- private void waitUntilCanRegister(String event, Semaphore waitObject) {
- try {
- assertTrue("Never registered event: " + event,
- waitObject.tryAcquire(LONG_TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail("Wait was interrupted");
- }
- }
-
- /** Schedules resuming all postponed events after SHORT_TIMEOUT_MS */
- private void scheduleResumeAllEventsLocked() {
- assertTrue(mResumeAllEventsCallback == null);
- mResumeAllEventsCallback = this::allEventsResumeCallback;
- POSTPONED_EVENT_RESUME_HANDLER.postDelayed(mResumeAllEventsCallback, SHORT_TIMEOUT_MS);
- }
-
- private synchronized void allEventsResumeCallback() {
- assertTrue("In callback, but callback is not set", mResumeAllEventsCallback != null);
- mResumeAllEventsCallback = null;
- registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
- }
-
- private void registerPostponedEventsLocked(Collection<String> events) {
- for (String event : events) {
- registerPostponedEventLocked(event);
- if (eventAsEnter(event) != null) {
- // Once :enter is registered, switch to waiting for :exit to come. Won't register
- // other postponed events.
- break;
- }
- }
- }
-
- private void registerPostponedEventLocked(String event) {
- mPostponedEvents.remove(event).release();
- registerEventLocked(event);
- }
-
- /**
- * If the last registered event was XXX:enter, returns XXX, otherwise, null.
- */
- private String lastEventAsEnter() {
- return eventAsEnter(mCurrentSequence.substring(mCurrentSequence.lastIndexOf("|") + 1));
- }
-
- /**
- * If the event is XXX:postfix, returns XXX, otherwise, null.
- */
- private static String prefixFromPostfixedEvent(String event, String postfix) {
- final int columnPos = event.indexOf(':');
- if (columnPos != -1 && postfix.equals(event.substring(columnPos + 1))) {
- return event.substring(0, columnPos);
- }
- return null;
- }
-
- /**
- * If the event is XXX:enter, returns XXX, otherwise, null.
- */
- private static String eventAsEnter(String event) {
- return prefixFromPostfixedEvent(event, ENTER_POSTFIX);
- }
-
- /**
- * If the event is XXX:exit, returns XXX, otherwise, null.
- */
- private static String eventAsExit(String event) {
- return prefixFromPostfixedEvent(event, EXIT_POSTFIX);
- }
-
- private void registerEventLocked(String event) {
- assertTrue(canRegisterEventNowLocked(event));
-
- Log.d(TAG, "Actually registering event: " + event);
- EventNode next = mLastRegisteredEvent.mNextEvents.get(event);
- if (next == null) {
- // This event wasn't seen after mLastRegisteredEvent.
- next = new EventNode();
- mLastRegisteredEvent.mNextEvents.put(event, next);
- // The fact that we've added a new event after the previous one means that the
- // previous event is still a growth point, unless this event is :exit, which means
- // that the previous event is :enter.
- mLastRegisteredEvent.mStoppedAddingChildren = eventAsExit(event) != null;
- }
-
- mLastRegisteredEvent = next;
- mRegisteredEventCount++;
-
- if (mCurrentSequence.length() > 0) mCurrentSequence.append("|");
- mCurrentSequence.append(event);
- Log.d(TAG, "Repro sequence: " + mCurrentSequence);
- }
-
- private void runResumeAllEventsCallbackLocked() {
- if (mResumeAllEventsCallback != null) {
- POSTPONED_EVENT_RESUME_HANDLER.removeCallbacks(mResumeAllEventsCallback);
- mResumeAllEventsCallback.run();
- }
- }
-
- private CharSequence dumpStateLocked() {
- StringBuilder sb = new StringBuilder();
-
- sb.append("Sequence to follow: ");
- for (String event : mSequenceToFollow) sb.append(" " + event);
- sb.append(".\n");
- sb.append("Registered event count: " + mRegisteredEventCount);
-
- sb.append("\nPostponed events: ");
- for (String event : mPostponedEvents.keySet()) sb.append(" " + event);
- sb.append(".");
-
- sb.append("\nNodes: \n");
- mRoot.debugDump(sb, 0, "");
- return sb;
- }
-
- public int numberOfLeafNodes() {
- return mRoot.numberOfLeafNodes();
- }
-
- private List<String> generateSequenceToFollowLocked() {
- ArrayList<String> sequence = new ArrayList<>();
- mRoot.populatePathToGrowthPoint(sequence);
- return sequence;
- }
-}
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
deleted file mode 100644
index 59f2173..0000000
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class RaceConditionReproducerTest {
- private final static String SOME_VALID_SEQUENCE_3_3 = "B1|A1|A2|B2|A3|B3";
-
- private static int factorial(int n) {
- int res = 1;
- for (int i = 2; i <= n; ++i) res *= i;
- return res;
- }
-
- RaceConditionReproducer eventProcessor;
-
- @Before
- public void setup() {
- eventProcessor = new RaceConditionReproducer();
- }
-
- @After
- public void tearDown() {
- TraceHelperForTest.cleanup();
- }
-
- private void run3_3_TestAction() throws InterruptedException {
- Thread tb = new Thread(() -> {
- eventProcessor.onEvent("B1");
- eventProcessor.onEvent("B2");
- eventProcessor.onEvent("B3");
- });
- tb.start();
-
- eventProcessor.onEvent("A1");
- eventProcessor.onEvent("A2");
- eventProcessor.onEvent("A3");
-
- tb.join();
- }
-
- @Test
- @Ignore // The test is too long for continuous testing.
- // 2 threads, 3 events each.
- public void test3_3() throws Exception {
- boolean sawTheValidSequence = false;
-
- for (; ; ) {
- eventProcessor.startIteration();
- run3_3_TestAction();
- final boolean needMoreIterations = eventProcessor.finishIteration();
-
- sawTheValidSequence = sawTheValidSequence ||
- SOME_VALID_SEQUENCE_3_3.equals(eventProcessor.getCurrentSequenceString());
-
- if (!needMoreIterations) break;
- }
-
- assertEquals("Wrong number of leaf nodes",
- factorial(3 + 3) / (factorial(3) * factorial(3)),
- eventProcessor.numberOfLeafNodes());
- assertTrue(sawTheValidSequence);
- }
-
- @Test
- @Ignore // The test is too long for continuous testing.
- // 2 threads, 3 events, including enter-exit pairs each.
- public void test3_3_enter_exit() throws Exception {
- boolean sawTheValidSequence = false;
-
- for (; ; ) {
- eventProcessor.startIteration();
- Thread tb = new Thread(() -> {
- eventProcessor.onEvent("B1:enter");
- eventProcessor.onEvent("B1:exit");
- eventProcessor.onEvent("B2");
- eventProcessor.onEvent("B3:enter");
- eventProcessor.onEvent("B3:exit");
- });
- tb.start();
-
- eventProcessor.onEvent("A1");
- eventProcessor.onEvent("A2:enter");
- eventProcessor.onEvent("A2:exit");
- eventProcessor.onEvent("A3:enter");
- eventProcessor.onEvent("A3:exit");
-
- tb.join();
- final boolean needMoreIterations = eventProcessor.finishIteration();
-
- sawTheValidSequence = sawTheValidSequence ||
- "B1:enter|B1:exit|A1|A2:enter|A2:exit|B2|A3:enter|A3:exit|B3:enter|B3:exit".
- equals(eventProcessor.getCurrentSequenceString());
-
- if (!needMoreIterations) break;
- }
-
- assertEquals("Wrong number of leaf nodes",
- factorial(3 + 3) / (factorial(3) * factorial(3)),
- eventProcessor.numberOfLeafNodes());
- assertTrue(sawTheValidSequence);
- }
-
- @Test
- // 2 threads, 3 events each; reproducing a particular event sequence.
- public void test3_3_ReproMode() throws Exception {
- eventProcessor = new RaceConditionReproducer(SOME_VALID_SEQUENCE_3_3);
- eventProcessor.startIteration();
- run3_3_TestAction();
- assertTrue(!eventProcessor.finishIteration());
- assertEquals(SOME_VALID_SEQUENCE_3_3, eventProcessor.getCurrentSequenceString());
-
- assertEquals("Wrong number of leaf nodes", 1, eventProcessor.numberOfLeafNodes());
- }
-
- @Test
- @Ignore // The test is too long for continuous testing.
- // 2 threads with 2 events; 1 thread with 1 event.
- public void test2_1_2() throws Exception {
- for (; ; ) {
- eventProcessor.startIteration();
- Thread tb = new Thread(() -> {
- eventProcessor.onEvent("B1");
- eventProcessor.onEvent("B2");
- });
- tb.start();
-
- Thread tc = new Thread(() -> {
- eventProcessor.onEvent("C1");
- });
- tc.start();
-
- eventProcessor.onEvent("A1");
- eventProcessor.onEvent("A2");
-
- tb.join();
- tc.join();
-
- if (!eventProcessor.finishIteration()) break;
- }
-
- assertEquals("Wrong number of leaf nodes",
- factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
- eventProcessor.numberOfLeafNodes());
- }
-
- @Test
- @Ignore // The test is too long for continuous testing.
- // 2 threads with 2 events; 1 thread with 1 event. Includes enter-exit pairs.
- public void test2_1_2_enter_exit() throws Exception {
- for (; ; ) {
- eventProcessor.startIteration();
- Thread tb = new Thread(() -> {
- eventProcessor.onEvent("B1:enter");
- eventProcessor.onEvent("B1:exit");
- eventProcessor.onEvent("B2:enter");
- eventProcessor.onEvent("B2:exit");
- });
- tb.start();
-
- Thread tc = new Thread(() -> {
- eventProcessor.onEvent("C1:enter");
- eventProcessor.onEvent("C1:exit");
- });
- tc.start();
-
- eventProcessor.onEvent("A1:enter");
- eventProcessor.onEvent("A1:exit");
- eventProcessor.onEvent("A2:enter");
- eventProcessor.onEvent("A2:exit");
-
- tb.join();
- tc.join();
-
- if (!eventProcessor.finishIteration()) break;
- }
-
- assertEquals("Wrong number of leaf nodes",
- factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
- eventProcessor.numberOfLeafNodes());
- }
-}
diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
index fb03fe1..cf80ece 100644
--- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt
+++ b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
@@ -23,12 +23,18 @@
import com.android.launcher3.tests.R as TestR
import kotlin.IntArray
-class TestResourceHelper(private val context: Context, private val specsFileId: Int) :
+class TestResourceHelper(private val context: Context, 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
+ val clone =
+ when {
+ styleId.contentEquals(R.styleable.SizeSpec) -> TestR.styleable.SizeSpec
+ styleId.contentEquals(R.styleable.WorkspaceSpec) -> TestR.styleable.WorkspaceSpec
+ styleId.contentEquals(R.styleable.FolderSpec) -> TestR.styleable.FolderSpec
+ styleId.contentEquals(R.styleable.AllAppsSpec) -> TestR.styleable.AllAppsSpec
+ else -> styleId.clone()
+ }
+
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 f8cd995..21059e6 100644
--- a/tests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -18,14 +18,13 @@
import static android.util.Base64.NO_PADDING;
import static android.util.Base64.NO_WRAP;
-import static androidx.test.InstrumentationRegistry.getContext;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
+import android.app.Instrumentation;
import android.app.blob.BlobHandle;
import android.app.blob.BlobStoreManager;
import android.content.Context;
@@ -35,9 +34,12 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
+import android.system.OsConstants;
import android.util.Base64;
+import android.util.Log;
import androidx.test.uiautomator.UiDevice;
@@ -52,26 +54,36 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
+import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
public class TestUtil {
+ private static final String TAG = "TestUtil";
+
public static final String DUMMY_PACKAGE = "com.example.android.aardwolf";
- public static final int DEFAULT_USER_ID = 0;
+ public static final String DUMMY_CLASS_NAME = "com.example.android.aardwolf.Activity1";
+ public static final long DEFAULT_UI_TIMEOUT = 10000;
public static void installDummyApp() throws IOException {
- installDummyAppForUser(DEFAULT_USER_ID);
+ final int defaultUserId = getMainUserId();
+ installDummyAppForUser(defaultUserId);
}
public static void installDummyAppForUser(int userId) throws IOException {
+ Instrumentation instrumentation = getInstrumentation();
// Copy apk from resources to a local file and install from there.
- final Resources resources = getContext().getResources();
+ final Resources resources = instrumentation.getContext().getResources();
final InputStream in = resources.openRawResource(
resources.getIdentifier("aardwolf_dummy_app",
- "raw", getContext().getPackageName()));
- final String apkFilename = getInstrumentation().getTargetContext().
- getFilesDir().getPath() + "/dummy_app.apk";
+ "raw", instrumentation.getContext().getPackageName()));
+ final String apkFilename = instrumentation.getTargetContext()
+ .getFilesDir().getPath() + "/dummy_app.apk";
try (PackageInstallCheck pic = new PackageInstallCheck()) {
final FileOutputStream out = new FileOutputStream(apkFilename);
@@ -84,7 +96,7 @@
in.close();
out.close();
- final String result = UiDevice.getInstance(getInstrumentation())
+ final String result = UiDevice.getInstance(instrumentation)
.executeShellCommand("pm install --user " + userId + " " + apkFilename);
Assert.assertTrue(
"Failed to install wellbeing test apk; make sure the device is rooted",
@@ -96,6 +108,23 @@
}
/**
+ * Returns the main user ID. NOTE: For headless system it is NOT 0. Returns 0 by default, if
+ * there is no main user.
+ *
+ * @return a main user ID
+ */
+ public static int getMainUserId() throws IOException {
+ Instrumentation instrumentation = getInstrumentation();
+ final String result = UiDevice.getInstance(instrumentation)
+ .executeShellCommand("cmd user get-main-user");
+ try {
+ return Integer.parseInt(result.trim());
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ /**
* Utility class to override a boolean flag during test. Note that the returned SafeCloseable
* must be closed to restore the original state
*/
@@ -159,6 +188,50 @@
Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, null);
}
+ /**
+ * Utility method to run a task synchronously which converts any exceptions to RuntimeException
+ */
+ public static void runOnExecutorSync(ExecutorService executor, UncheckedRunnable task) {
+ try {
+ executor.submit(() -> {
+ try {
+ task.run();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }).get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Runs the callback on the UI thread and returns the result.
+ */
+ public static <T> T getOnUiThread(final Callable<T> callback) {
+ try {
+ FutureTask<T> task = new FutureTask<>(callback);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ task.run();
+ } else {
+ new Handler(Looper.getMainLooper()).post(task);
+ }
+ return task.get(DEFAULT_UI_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e);
+ Process.sendSignal(Process.myPid(), OsConstants.SIGABRT);
+ throw new RuntimeException(e);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Interface to indicate a runnable which can throw any exception. */
+ public interface UncheckedRunnable {
+ /** Method to run the task */
+ void run() throws Exception;
+ }
+
private static class PackageInstallCheck extends LauncherApps.Callback
implements AutoCloseable {
@@ -166,7 +239,8 @@
final LauncherApps mLauncherApps;
PackageInstallCheck() {
- mLauncherApps = getTargetContext().getSystemService(LauncherApps.class);
+ mLauncherApps = getInstrumentation().getTargetContext()
+ .getSystemService(LauncherApps.class);
mLauncherApps.registerCallback(this, new Handler(Looper.getMainLooper()));
}
diff --git a/tests/src/com/android/launcher3/util/TraceHelperForTest.java b/tests/src/com/android/launcher3/util/TraceHelperForTest.java
deleted file mode 100644
index f1c8a67..0000000
--- a/tests/src/com/android/launcher3/util/TraceHelperForTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import java.util.LinkedList;
-import java.util.function.IntConsumer;
-
-public class TraceHelperForTest extends TraceHelper {
-
- private static final TraceHelperForTest INSTANCE_FOR_TEST = new TraceHelperForTest();
-
- private final ThreadLocal<LinkedList<TraceInfo>> mStack =
- ThreadLocal.withInitial(LinkedList::new);
-
- private RaceConditionReproducer mRaceConditionReproducer;
- private IntConsumer mFlagsChangeListener;
-
- public static void setRaceConditionReproducer(RaceConditionReproducer reproducer) {
- TraceHelper.INSTANCE = INSTANCE_FOR_TEST;
- INSTANCE_FOR_TEST.mRaceConditionReproducer = reproducer;
- }
-
- public static void cleanup() {
- INSTANCE_FOR_TEST.mRaceConditionReproducer = null;
- INSTANCE_FOR_TEST.mFlagsChangeListener = null;
- }
-
- public static void setFlagsChangeListener(IntConsumer listener) {
- TraceHelper.INSTANCE = INSTANCE_FOR_TEST;
- INSTANCE_FOR_TEST.mFlagsChangeListener = listener;
- }
-
- private TraceHelperForTest() { }
-
- @Override
- public Object beginSection(String sectionName, int flags) {
- LinkedList<TraceInfo> stack = mStack.get();
- TraceInfo info = new TraceInfo(sectionName, flags);
- stack.add(info);
-
- if ((flags & TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS) != 0
- && mRaceConditionReproducer != null) {
- mRaceConditionReproducer.onEvent(RaceConditionReproducer.enterEvt(sectionName));
- }
- updateBinderTracking(stack);
-
- super.beginSection(sectionName, flags);
- return info;
- }
-
- @Override
- public void endSection(Object token) {
- LinkedList<TraceInfo> stack = mStack.get();
- if (stack.size() == 0) {
- new Throwable().printStackTrace();
- }
- TraceInfo info = (TraceInfo) token;
- stack.remove(info);
- if ((info.flags & TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS) != 0
- && mRaceConditionReproducer != null) {
- mRaceConditionReproducer.onEvent(RaceConditionReproducer.exitEvt(info.sectionName));
- }
- updateBinderTracking(stack);
-
- super.endSection(token);
- }
-
- @Override
- public Object beginFlagsOverride(int flags) {
- LinkedList<TraceInfo> stack = mStack.get();
- TraceInfo info = new TraceInfo(null, flags);
- stack.add(info);
- updateBinderTracking(stack);
- super.beginFlagsOverride(flags);
- return info;
- }
-
- @Override
- public void endFlagsOverride(Object token) {
- super.endFlagsOverride(token);
- LinkedList<TraceInfo> stack = mStack.get();
- TraceInfo info = (TraceInfo) token;
- stack.remove(info);
- updateBinderTracking(stack);
- }
-
- private void updateBinderTracking(LinkedList<TraceInfo> stack) {
- if (mFlagsChangeListener != null) {
- mFlagsChangeListener.accept(stack.stream()
- .mapToInt(info -> info.flags).reduce(0, (a, b) -> a | b));
- }
- }
-
- private static class TraceInfo {
- public final String sectionName;
- public final int flags;
-
- TraceInfo(String sectionName, int flags) {
- this.sectionName = sectionName;
- this.flags = flags;
- }
- }
-}
diff --git a/tests/src/com/android/launcher3/util/WidgetUtils.java b/tests/src/com/android/launcher3/util/WidgetUtils.java
index b0df055..027a31a 100644
--- a/tests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/src/com/android/launcher3/util/WidgetUtils.java
@@ -18,18 +18,14 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import android.os.Process;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
@@ -88,33 +84,6 @@
}
/**
- * Adds {@param item} on the homescreen on the 0th screen
- */
- public static void addItemToScreen(ItemInfo item, Context targetContext) {
- ContentResolver resolver = targetContext.getContentResolver();
- int screenId = FIRST_SCREEN_ID;
- // Update the screen id counter for the provider.
- LauncherSettings.Settings.call(resolver,
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
- if (screenId > FIRST_SCREEN_ID) {
- screenId = FIRST_SCREEN_ID;
- }
-
- // Insert the item
- ContentWriter writer = new ContentWriter(targetContext);
- item.id = LauncherSettings.Settings.call(
- resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
- item.screenId = screenId;
- item.onAddToDatabase(writer);
- writer.put(LauncherSettings.Favorites._ID, item.id);
- resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
- writer.getValues(targetContext));
- }
-
-
- /**
* Creates a {@link AppWidgetProviderInfo} for the provided component name
*/
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 7ca6a06..62d70ad 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -8,10 +8,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
import androidx.test.uiautomator.UiDevice;
-import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.data.ExportedData;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -24,22 +23,21 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class FailureWatcher extends TestWatcher {
private static final String TAG = "FailureWatcher";
private static boolean sSavedBugreport = false;
- final private UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
@NonNull
- private final ViewCapture mViewCapture;
+ private final Supplier<ExportedData> mViewCaptureDataSupplier;
- public FailureWatcher(UiDevice device, LauncherInstrumentation launcher,
- @NonNull ViewCapture viewCapture) {
- mDevice = device;
+ public FailureWatcher(LauncherInstrumentation launcher,
+ @NonNull Supplier<ExportedData> viewCaptureDataSupplier) {
mLauncher = launcher;
- mViewCapture = viewCapture;
+ mViewCaptureDataSupplier = viewCaptureDataSupplier;
}
@Override
@@ -71,7 +69,7 @@
@Override
protected void failed(Throwable e, Description description) {
- onError(mLauncher, description, e, mViewCapture);
+ onError(mLauncher, description, e, mViewCaptureDataSupplier);
}
static File diagFile(Description description, String prefix, String ext) {
@@ -86,7 +84,7 @@
}
private static void onError(LauncherInstrumentation launcher, Description description,
- Throwable e, @Nullable ViewCapture viewCapture) {
+ Throwable e, @Nullable Supplier<ExportedData> viewCaptureDataSupplier) {
final File sceenshot = diagFile(description, "TestScreenshot", "png");
final File hierarchy = diagFile(description, "Hierarchy", "zip");
@@ -103,9 +101,10 @@
dumpCommand("cmd window dump-visible-window-views", out);
out.closeEntry();
- if (viewCapture != null) {
+ if (viewCaptureDataSupplier != null) {
out.putNextEntry(new ZipEntry("FS/data/misc/wmtrace/failed_test.vc"));
- viewCapture.dumpTo(out, ApplicationProvider.getApplicationContext());
+ final ExportedData exportedData = viewCaptureDataSupplier.get();
+ if (exportedData != null) exportedData.writeTo(out);
out.closeEntry();
}
} catch (Exception ignored) {
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
deleted file mode 100644
index e9a52f8..0000000
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.android.launcher3.util.rule;
-
-import android.app.Activity;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
-
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.concurrent.Callable;
-
-/**
- * Test rule to get the current Launcher activity.
- */
-public class LauncherActivityRule extends SimpleActivityRule<Launcher> {
-
- public LauncherActivityRule() {
- super(Launcher.class);
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
-
- return new MyStatement(base) {
- @Override
- public void onActivityStarted(Activity activity) {
- if (activity instanceof Launcher) {
- ((Launcher) activity).getRotationHelper().forceAllowRotationForTesting(true);
- }
- }
- };
- }
-
- public Callable<Boolean> itemExists(final ItemOperator op) {
- return () -> {
- Launcher launcher = getActivity();
- if (launcher == null) {
- return false;
- }
- return launcher.getWorkspace().getFirstMatch(op) != null;
- };
- }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
deleted file mode 100644
index 2eedec3..0000000
--- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.android.launcher3.util.rule;
-
-import android.app.Activity;
-import android.app.Application;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.os.Bundle;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Test rule to get the current activity.
- */
-public class SimpleActivityRule<T extends Activity> implements TestRule {
-
- private final Class<T> mClass;
- private T mActivity;
-
- public SimpleActivityRule(Class<T> clazz) {
- mClass = clazz;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new MyStatement(base);
- }
-
- public T getActivity() {
- return mActivity;
- }
-
- protected class MyStatement extends Statement implements ActivityLifecycleCallbacks {
-
- private final Statement mBase;
-
- public MyStatement(Statement base) {
- mBase = base;
- }
-
- @Override
- public void evaluate() throws Throwable {
- Application app = (Application)
- InstrumentationRegistry.getTargetContext().getApplicationContext();
- app.registerActivityLifecycleCallbacks(this);
- try {
- mBase.evaluate();
- } finally {
- app.unregisterActivityLifecycleCallbacks(this);
- mActivity = null;
- }
- }
-
- @Override
- public void onActivityCreated(Activity activity, Bundle bundle) {
- if (activity != null && mClass.isInstance(activity)) {
- mActivity = (T) activity;
- }
- }
-
- @Override
- public void onActivityStarted(Activity activity) {
- }
-
- @Override
- public void onActivityResumed(Activity activity) {
- }
-
- @Override
- public void onActivityPaused(Activity activity) {
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- }
-
- @Override
- public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- if (activity == mActivity) {
- mActivity = null;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index f33a50a..38de071 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -17,6 +17,8 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assume.assumeTrue;
+
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
@@ -69,12 +71,9 @@
return new Statement() {
@Override
public void evaluate() throws Throwable {
- if ((stability.flavors() & getRunFlavor()) != 0) {
- Log.d(TAG, "Running " + description.getDisplayName());
- base.evaluate();
- } else {
- Log.d(TAG, "Skipping " + description.getDisplayName());
- }
+ assumeTrue("Ignoring the test due to @Stability annotation",
+ (stability.flavors() & getRunFlavor()) != 0);
+ base.evaluate();
}
};
} else {
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
index 0c65539..ccbae4f 100644
--- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
+++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
@@ -20,9 +20,21 @@
import android.media.permission.SafeCloseable
import android.os.Bundle
import androidx.test.core.app.ApplicationProvider
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.app.viewcapture.SimpleViewCapture
import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR
+import com.android.app.viewcapture.data.ExportedData
+import com.android.launcher3.tapl.TestHelpers
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
+import com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.OutputStreamWriter
+import java.util.function.Supplier
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@@ -33,28 +45,33 @@
*
* This rule will not work in OOP tests that don't have access to the activity under test.
*/
-class ViewCaptureRule : TestRule {
- val viewCapture = SimpleViewCapture("test-view-capture")
+class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : TestRule {
+ private val viewCapture = SimpleViewCapture("test-view-capture")
+ var viewCaptureData: ExportedData? = null
+ private set
override fun apply(base: Statement, description: Description): Statement {
+ // Skip view capture collection in Launcher3 tests to avoid hidden API check exception.
+ if (
+ "com.android.launcher3.tests" ==
+ InstrumentationRegistry.getInstrumentation().context.packageName
+ )
+ return base
+
return object : Statement() {
override fun evaluate() {
+ viewCaptureData = null
val windowListenerCloseables = mutableListOf<SafeCloseable>()
+ startCapturingExistingActivity(windowListenerCloseables)
+
val lifecycleCallbacks =
object : ActivityLifecycleCallbacksAdapter {
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
- super.onActivityCreated(activity, bundle)
- windowListenerCloseables.add(
- viewCapture.startCapture(
- activity.window.decorView,
- "${description.testClass?.simpleName}.${description.methodName}"
- )
- )
+ startCapture(windowListenerCloseables, activity)
}
override fun onActivityDestroyed(activity: Activity) {
- super.onActivityDestroyed(activity)
viewCapture.stopCapture(activity.window.decorView)
}
}
@@ -67,6 +84,9 @@
} finally {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
+ viewCaptureData =
+ viewCapture.getExportedData(ApplicationProvider.getApplicationContext())
+
// Clean up ViewCapture references here rather than in onActivityDestroyed so
// test code can access view hierarchy capture. onActivityDestroyed would delete
// view capture data before FailureWatcher could output it as a test artifact.
@@ -74,7 +94,69 @@
// is removed while onDraw is running, resulting in an IllegalStateException.
MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) }
}
+
+ analyzeViewCapture(description)
}
+
+ private fun startCapturingExistingActivity(
+ windowListenerCloseables: MutableCollection<SafeCloseable>
+ ) {
+ val alreadyOpenActivity = alreadyOpenActivitySupplier.get()
+ if (alreadyOpenActivity != null) {
+ startCapture(windowListenerCloseables, alreadyOpenActivity)
+ }
+ }
+
+ private fun startCapture(
+ windowListenerCloseables: MutableCollection<SafeCloseable>,
+ activity: Activity
+ ) {
+ windowListenerCloseables.add(
+ viewCapture.startCapture(
+ activity.window.decorView,
+ "${description.testClass?.simpleName}.${description.methodName}"
+ )
+ )
+ }
+ }
+ }
+
+ private fun analyzeViewCapture(description: Description) {
+ // OOP tests don't produce ViewCapture data
+ if (!TestHelpers.isInLauncherProcess()) return
+
+ // Due to flakiness of ViewCapture verifier, don't run the check in presubmit
+ if (TestStabilityRule.getRunFlavor() != PLATFORM_POSTSUBMIT) return
+
+ var frameCount = 0
+ for (i in 0 until viewCaptureData!!.windowDataCount) {
+ frameCount += viewCaptureData!!.getWindowData(i).frameDataCount
+ }
+ assertTrue("Empty ViewCapture data", frameCount > 0)
+
+ val anomalies: Map<String, String> = ViewCaptureAnalyzer.getAnomalies(viewCaptureData)
+ if (!anomalies.isEmpty()) {
+ val diagFile = FailureWatcher.diagFile(description, "ViewAnomalies", "txt")
+ try {
+ OutputStreamWriter(BufferedOutputStream(FileOutputStream(diagFile))).use { writer ->
+ writer.write("View animation anomalies detected.\r\n")
+ writer.write(
+ "To suppress an anomaly for a view, add its full path to the PATHS_TO_IGNORE list in the corresponding AnomalyDetector.\r\n"
+ )
+ writer.write("List of views with animation anomalies:\r\n")
+
+ for ((viewPath, message) in anomalies) {
+ writer.write("View: $viewPath\r\n $message\r\n")
+ }
+ }
+ } catch (ex: IOException) {
+ throw RuntimeException(ex)
+ }
+
+ val (viewPath, message) = anomalies.entries.first()
+ fail(
+ "${anomalies.size} view(s) had animation anomalies during the test, including view: $viewPath: $message\r\nSee ${diagFile.name} for details."
+ )
}
}
}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
new file mode 100644
index 0000000..4b65439
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -0,0 +1,205 @@
+/*
+ * 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.viewcapture_analysis;
+
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
+
+import java.util.List;
+
+/**
+ * Anomaly detector that triggers an error when alpha of a view changes too rapidly.
+ * Invisible views are treated as if they had zero alpha.
+ */
+final class AlphaJumpDetector extends AnomalyDetector {
+ // Commonly used parts of the paths to ignore.
+ private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|";
+ private static final String DRAG_LAYER =
+ CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|";
+ private static final String RECENTS_DRAG_LAYER =
+ CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|";
+
+ private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
+ CONTENT
+ + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
+ + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
+ + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
+ + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge",
+ CONTENT
+ + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
+ + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
+ + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
+ + "|WidgetCellPreview:id/widget_preview_container|WidgetCell$1|FrameLayout"
+ + "|ImageView:id/icon",
+ CONTENT + "AddItemDragLayer:id/add_item_drag_layer|View",
+ DRAG_LAYER
+ + "AppWidgetResizeFrame|FrameLayout|ImageButton:id/widget_reconfigure_button",
+ DRAG_LAYER
+ + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_bottom_handle",
+ DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_frame",
+ DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_left_handle",
+ DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_right_handle",
+ DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_top_handle",
+ DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail",
+ DRAG_LAYER + "FloatingTaskView|SplitPlaceholderView:id/split_placeholder",
+ DRAG_LAYER + "Folder|FolderPagedView:id/folder_content",
+ DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view",
+ DRAG_LAYER + "LauncherDragView",
+ DRAG_LAYER + "LauncherRecentsView:id/overview_panel",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view|FrameLayout:id"
+ + "/select_mode_buttons|ImageButton:id/close",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
+ + "/action_buttons|Button:id/action_screenshot",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
+ + "/action_buttons|Button:id/action_select",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
+ + "/action_buttons|Button:id/action_split",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
+ + "/action_buttons|Space:id/action_split_space",
+ DRAG_LAYER
+ + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material"
+ + "|DeepShortcutTextView:id/bubble_text",
+ DRAG_LAYER
+ + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material|View"
+ + ":id/icon",
+ DRAG_LAYER
+ + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ + "/system_shortcuts_container|DeepShortcutView:id/system_shortcut"
+ + "|BubbleTextView:id/bubble_text",
+ DRAG_LAYER
+ + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ + "/system_shortcuts_container|DeepShortcutView:id/system_shortcut|View:id"
+ + "/icon",
+ DRAG_LAYER
+ + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ + "/system_shortcuts_container|ImageView",
+ DRAG_LAYER
+ + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ + "/widget_shortcut_container|DeepShortcutView:id/system_shortcut"
+ + "|BubbleTextView:id/bubble_text",
+ DRAG_LAYER
+ + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ + "/widget_shortcut_container|DeepShortcutView:id/system_shortcut|View:id/icon",
+ DRAG_LAYER + "SearchContainerView:id/apps_view",
+ DRAG_LAYER + "Snackbar|TextView:id/action",
+ DRAG_LAYER + "Snackbar|TextView:id/label",
+ DRAG_LAYER + "SplitInstructionsView|AppCompatTextView:id/split_instructions_text",
+ DRAG_LAYER + "TaskMenuView|LinearLayout:id/menu_option_layout",
+ DRAG_LAYER + "TaskMenuViewWithArrow|LinearLayout:id/menu_option_layout",
+ DRAG_LAYER + "TaskMenuView|TextView:id/task_name",
+ DRAG_LAYER + "View",
+ DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container",
+ DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container",
+ CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
+ RECENTS_DRAG_LAYER + "ArrowTipView",
+ DRAG_LAYER + "ArrowTipView",
+ DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
+ RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view"
+ + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+ RECENTS_DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view"
+ + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view"
+ + "|LinearLayout:id/action_buttons|Button:id/action_select",
+ RECENTS_DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view"
+ + "|LinearLayout:id/action_buttons|Button:id/action_select",
+ DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view"
+ + "|LinearLayout:id/action_buttons|Button:id/action_split",
+ RECENTS_DRAG_LAYER
+ + "NexusOverviewActionsView:id/overview_actions_view"
+ + "|LinearLayout:id/action_buttons|Button:id/action_split",
+ DRAG_LAYER + "IconView"
+ ));
+
+ // Minimal increase or decrease of view's alpha between frames that triggers the error.
+ private static final float ALPHA_JUMP_THRESHOLD = 1f;
+
+ // Per-AnalysisNode data that's specific to this detector.
+ private static class NodeData {
+ public boolean ignoreAlphaJumps;
+
+ // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is
+ // ignored.
+ // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no
+ // children.
+ public IgnoreNode ignoreNode;
+ }
+
+ private NodeData getNodeData(AnalysisNode info) {
+ return (NodeData) info.detectorsData[detectorOrdinal];
+ }
+
+ @Override
+ void initializeNode(AnalysisNode info) {
+ final NodeData nodeData = new NodeData();
+ info.detectorsData[detectorOrdinal] = nodeData;
+
+ // If the parent view ignores alpha jumps, its descendants will too.
+ final boolean parentIgnoresAlphaJumps = info.parent != null && getNodeData(
+ info.parent).ignoreAlphaJumps;
+ if (parentIgnoresAlphaJumps) {
+ nodeData.ignoreAlphaJumps = true;
+ return;
+ }
+
+ // Parent view doesn't ignore alpha jumps.
+ // Initialize this AnalysisNode's ignore-node with the corresponding child of the
+ // ignore-node of the parent, if present.
+ final IgnoreNode parentIgnoreNode = info.parent != null
+ ? getNodeData(info.parent).ignoreNode
+ : IGNORED_NODES_ROOT;
+ nodeData.ignoreNode = parentIgnoreNode != null
+ ? parentIgnoreNode.children.get(info.nodeIdentity) : null;
+ // AnalysisNode will be ignored if the corresponding ignore-node is a leaf.
+ nodeData.ignoreAlphaJumps =
+ nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty();
+ }
+
+ @Override
+ String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp,
+ int windowSizePx) {
+ // If the view was previously seen, proceed with analysis only if it was present in the
+ // view hierarchy in the previous frame.
+ if (oldInfo != null && oldInfo.frameN != frameN) return null;
+
+ final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo;
+ final NodeData nodeData = getNodeData(latestInfo);
+ if (nodeData.ignoreAlphaJumps) return null;
+
+ final float oldAlpha = oldInfo != null ? oldInfo.alpha : 0;
+ final float newAlpha = newInfo != null ? newInfo.alpha : 0;
+ final float alphaDeltaAbs = Math.abs(newAlpha - oldAlpha);
+
+ if (alphaDeltaAbs >= ALPHA_JUMP_THRESHOLD) {
+ nodeData.ignoreAlphaJumps = true; // No need to report alpha jump in children.
+ return String.format(
+ "Alpha jump detected: alpha change: %s (%s -> %s), threshold: %s",
+ alphaDeltaAbs, oldAlpha, newAlpha, ALPHA_JUMP_THRESHOLD);
+ }
+ return null;
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java
new file mode 100644
index 0000000..786791c
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java
@@ -0,0 +1,85 @@
+/*
+ * 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.viewcapture_analysis;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Detector of one kind of anomaly.
+ */
+abstract class AnomalyDetector {
+ // Index of this detector in ViewCaptureAnalyzer.ANOMALY_DETECTORS
+ public int detectorOrdinal;
+
+ /**
+ * Element of the tree of ignored nodes.
+ * If the "children" map is empty, then this node should be ignored, i.e. the analysis shouldn't
+ * run for it.
+ * I.e. ignored nodes correspond to the leaves in the ignored nodes tree.
+ */
+ protected static class IgnoreNode {
+ // Map from child node identities to ignore-nodes for these children.
+ public final Map<String, IgnoreNode> children = new HashMap<>();
+ }
+
+ // Converts the list of full paths of nodes to ignore to a more efficient tree of ignore-nodes.
+ protected static IgnoreNode buildIgnoreNodesTree(Iterable<String> pathsToIgnore) {
+ final IgnoreNode root = new IgnoreNode();
+ for (String pathToIgnore : pathsToIgnore) {
+ // Scan the diag path of an ignored node and add its elements into the tree.
+ IgnoreNode currentIgnoreNode = root;
+ for (String part : pathToIgnore.split("\\|")) {
+ // Ensure that the child of the node is added to the tree.
+ IgnoreNode child = currentIgnoreNode.children.get(part);
+ if (child == null) {
+ currentIgnoreNode.children.put(part, child = new IgnoreNode());
+ }
+ currentIgnoreNode = child;
+ }
+ }
+ return root;
+ }
+
+ /**
+ * Initializes fields of the node that are specific to the anomaly detected by this
+ * detector.
+ */
+ abstract void initializeNode(@NonNull ViewCaptureAnalyzer.AnalysisNode info);
+
+ /**
+ * Detects anomalies by looking at the last occurrence of a view, and the current one.
+ * null value means that the view. 'oldInfo' and 'newInfo' cannot be both null.
+ * If an anomaly is detected, an exception will be thrown.
+ *
+ * @param oldInfo the view, as seen in the last frame that contained it in the view
+ * hierarchy before 'currentFrame'. 'null' means that the view is first seen
+ * in the 'currentFrame'.
+ * @param newInfo the view in the view hierarchy of the 'currentFrame'. 'null' means that
+ * the view is not present in the 'currentFrame', but was present in the
+ * previous frame.
+ * @param frameN number of the current frame.
+ * @param windowSizePx maximum of the window width and height, in pixels.
+ * @return Anomaly diagnostic message if an anomaly has been detected; null otherwise.
+ */
+ abstract String detectAnomalies(
+ @Nullable ViewCaptureAnalyzer.AnalysisNode oldInfo,
+ @Nullable ViewCaptureAnalyzer.AnalysisNode newInfo, int frameN,
+ long frameTimeNs, int windowSizePx);
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
new file mode 100644
index 0000000..8b88ace
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
@@ -0,0 +1,176 @@
+/*
+ * 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.viewcapture_analysis;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
+
+import java.util.List;
+
+/**
+ * Anomaly detector that triggers an error when a view flashes, i.e. appears or disappears for a too
+ * short period of time.
+ */
+final class FlashDetector extends AnomalyDetector {
+ // Maximum time period of a view visibility or invisibility that is recognized as a flash.
+ private static final int FLASH_DURATION_MS = 300;
+
+ // Commonly used parts of the paths to ignore.
+ private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|";
+ private static final String DRAG_LAYER =
+ CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|";
+ private static final String RECENTS_DRAG_LAYER =
+ CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|";
+
+ private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
+ CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
+ DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView|TextView",
+ DRAG_LAYER
+ + "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id"
+ + "/apps_list_view|BubbleTextView:id/icon",
+ CONTENT
+ + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
+ + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
+ + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
+ + "|WidgetCellPreview:id/widget_preview_container|WidgetImageView:id"
+ + "/widget_preview",
+ CONTENT
+ + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
+ + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
+ + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
+ + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge",
+ RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView|IconView:id/icon",
+ DRAG_LAYER + "SearchContainerView:id/apps_view",
+ DRAG_LAYER + "LauncherDragView",
+ DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail",
+ DRAG_LAYER
+ + "WidgetsFullSheet|SpringRelativeLayout:id/container|WidgetsRecyclerView:id"
+ + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header",
+ DRAG_LAYER
+ + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container|LinearLayout:id"
+ + "/linear_layout_container|FrameLayout:id/recycler_view_container"
+ + "|FrameLayout:id/widgets_two_pane_sheet_recyclerview|WidgetsRecyclerView:id"
+ + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header"
+ ));
+
+ // Per-AnalysisNode data that's specific to this detector.
+ private static class NodeData {
+ public boolean ignoreFlashes;
+
+ // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is
+ // ignored.
+ // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no
+ // children.
+ public IgnoreNode ignoreNode;
+ }
+
+ private NodeData getNodeData(AnalysisNode info) {
+ return (NodeData) info.detectorsData[detectorOrdinal];
+ }
+
+ @Override
+ void initializeNode(AnalysisNode info) {
+ final NodeData nodeData = new NodeData();
+ info.detectorsData[detectorOrdinal] = nodeData;
+
+ // If the parent view ignores flashes, its descendants will too.
+ final boolean parentIgnoresFlashes = info.parent != null && getNodeData(
+ info.parent).ignoreFlashes;
+ if (parentIgnoresFlashes) {
+ nodeData.ignoreFlashes = true;
+ return;
+ }
+
+ // Parent view doesn't ignore flashes.
+ // Initialize this AnalysisNode's ignore-node with the corresponding child of the
+ // ignore-node of the parent, if present.
+ final IgnoreNode parentIgnoreNode = info.parent != null
+ ? getNodeData(info.parent).ignoreNode
+ : IGNORED_NODES_ROOT;
+ nodeData.ignoreNode = parentIgnoreNode != null
+ ? parentIgnoreNode.children.get(info.nodeIdentity) : null;
+ // AnalysisNode will be ignored if the corresponding ignore-node is a leaf.
+ nodeData.ignoreFlashes =
+ nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty();
+ }
+
+ @Override
+ String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN,
+ long frameTimeNs, int windowSizePx) {
+ // Should we check when a view was visible for a short period, then its alpha became 0?
+ // Then 'lastVisible' time should be the last one still visible?
+ // Check only transitions of alpha between 0 and 1?
+
+ // If this is the first time ever when we see the view, there have been no flashes yet.
+ if (oldInfo == null) return null;
+
+ // A flash requires a view to go from the full visibility to no-visibility and then back,
+ // or vice versa.
+ // If the last time the view was seen before the current frame, it didn't have full
+ // visibility; no flash can possibly be detected at the current frame.
+ if (oldInfo.alpha < 1) return null;
+
+ final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo;
+ final NodeData nodeData = getNodeData(latestInfo);
+ if (nodeData.ignoreFlashes) return null;
+
+ // Once the view becomes invisible, see for how long it was visible prior to that. If it
+ // was visible only for a short interval of time, it's a flash.
+ if (
+ // View is invisible in the current frame
+ newInfo == null
+ // When the view became visible last time, it was a transition from
+ // no-visibility to full visibility.
+ && oldInfo.timeBecameVisibleNs != -1) {
+ final long wasVisibleTimeMs = (frameTimeNs - oldInfo.timeBecameVisibleNs) / 1000000;
+
+ if (wasVisibleTimeMs <= FLASH_DURATION_MS) {
+ nodeData.ignoreFlashes = true; // No need to report flashes in children.
+ return
+ String.format(
+ "View was visible for a too short period of time %dms, which is a"
+ + " flash",
+ wasVisibleTimeMs
+ );
+ }
+ }
+
+ // Once a view becomes visible, see for how long it was invisible prior to that. If it
+ // was invisible only for a short interval of time, it's a flash.
+ if (
+ // The view is fully visible now
+ newInfo != null && newInfo.alpha >= 1
+ // The view wasn't visible in the previous frame
+ && frameN != oldInfo.frameN + 1) {
+ // We can assert the below condition because at this point, we know that
+ // oldInfo.alpha >= 1, i.e. it disappeared abruptly.
+ assertTrue("oldInfo.timeBecameInvisibleNs must not be -1",
+ oldInfo.timeBecameInvisibleNs != -1);
+
+ final long wasInvisibleTimeMs = (frameTimeNs - oldInfo.timeBecameInvisibleNs) / 1000000;
+ if (wasInvisibleTimeMs <= FLASH_DURATION_MS) {
+ nodeData.ignoreFlashes = true; // No need to report flashes in children.
+ return
+ String.format(
+ "View was invisible for a too short period of time %dms, which "
+ + "is a flash",
+ wasInvisibleTimeMs);
+ }
+ }
+ return null;
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java
new file mode 100644
index 0000000..a1ddcb0
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java
@@ -0,0 +1,126 @@
+/*
+ * 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.viewcapture_analysis;
+
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
+
+import java.util.List;
+
+/**
+ * Anomaly detector that triggers an error when a view position jumps.
+ */
+final class PositionJumpDetector extends AnomalyDetector {
+ // Maximum allowed jump in "milliwindows", i.e. a 1/1000's of the maximum of the window
+ // dimensions.
+ private static final float JUMP_MIW = 250;
+
+ private static final String[] BORDER_NAMES = {"left", "top", "right", "bottom"};
+
+ // Commonly used parts of the paths to ignore.
+ private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|";
+ private static final String DRAG_LAYER =
+ CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|";
+ private static final String RECENTS_DRAG_LAYER =
+ CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|";
+
+ private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
+ DRAG_LAYER + "SearchContainerView:id/apps_view",
+ DRAG_LAYER + "AppWidgetResizeFrame",
+ DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view",
+ CONTENT
+ + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
+ + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content",
+ DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container",
+ DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container",
+ DRAG_LAYER + "LauncherDragView",
+ RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView",
+ CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
+ DRAG_LAYER + "FloatingTaskView",
+ DRAG_LAYER + "LauncherRecentsView:id/overview_panel"
+ ));
+
+ // Per-AnalysisNode data that's specific to this detector.
+ private static class NodeData {
+ public boolean ignoreJumps;
+
+ // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is
+ // ignored.
+ // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no
+ // children.
+ public IgnoreNode ignoreNode;
+ }
+
+ private NodeData getNodeData(AnalysisNode info) {
+ return (NodeData) info.detectorsData[detectorOrdinal];
+ }
+
+ @Override
+ void initializeNode(AnalysisNode info) {
+ final NodeData nodeData = new NodeData();
+ info.detectorsData[detectorOrdinal] = nodeData;
+
+ // If the parent view ignores jumps, its descendants will too.
+ final boolean parentIgnoresJumps = info.parent != null && getNodeData(
+ info.parent).ignoreJumps;
+ if (parentIgnoresJumps) {
+ nodeData.ignoreJumps = true;
+ return;
+ }
+
+ // Parent view doesn't ignore jumps.
+ // Initialize this AnalysisNode's ignore-node with the corresponding child of the
+ // ignore-node of the parent, if present.
+ final IgnoreNode parentIgnoreNode = info.parent != null
+ ? getNodeData(info.parent).ignoreNode
+ : IGNORED_NODES_ROOT;
+ nodeData.ignoreNode = parentIgnoreNode != null
+ ? parentIgnoreNode.children.get(info.nodeIdentity) : null;
+ // AnalysisNode will be ignored if the corresponding ignore-node is a leaf.
+ nodeData.ignoreJumps =
+ nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty();
+ }
+
+ @Override
+ String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN,
+ long frameTimeNs, int windowSizePx) {
+ // If the view is not present in the current frame, there can't be a jump detected in the
+ // current frame.
+ if (newInfo == null) return null;
+
+ // We only detect position jumps if the view was visible in the previous frame.
+ if (oldInfo == null || frameN != oldInfo.frameN + 1) return null;
+
+ final NodeData newNodeData = getNodeData(newInfo);
+ if (newNodeData.ignoreJumps) return null;
+
+ final float[] positionDiffs = {
+ newInfo.left - oldInfo.left,
+ newInfo.top - oldInfo.top,
+ newInfo.right - oldInfo.right,
+ newInfo.bottom - oldInfo.bottom
+ };
+
+ for (int i = 0; i < 4; ++i) {
+ final float positionDiffAbs = Math.abs(positionDiffs[i]);
+ if (positionDiffAbs * 1000 > JUMP_MIW * windowSizePx) {
+ newNodeData.ignoreJumps = true;
+ return String.format("Position jump: %s jumped by %s",
+ BORDER_NAMES[i], positionDiffAbs);
+ }
+ }
+ return null;
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
new file mode 100644
index 0000000..9459cc2
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
@@ -0,0 +1,315 @@
+/*
+ * 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.viewcapture_analysis;
+
+import static android.view.View.VISIBLE;
+
+import com.android.app.viewcapture.data.ExportedData;
+import com.android.app.viewcapture.data.FrameData;
+import com.android.app.viewcapture.data.ViewNode;
+import com.android.app.viewcapture.data.WindowData;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility that analyzes ViewCapture data and finds anomalies such as views appearing or
+ * disappearing without alpha-fading.
+ */
+public class ViewCaptureAnalyzer {
+ private static final String SCRIM_VIEW_CLASS = "com.android.launcher3.views.ScrimView";
+
+ // All detectors. They will be invoked in the order listed here.
+ private static final AnomalyDetector[] ANOMALY_DETECTORS = {
+ new AlphaJumpDetector(),
+ new FlashDetector(),
+ new PositionJumpDetector()
+ };
+
+ static {
+ for (int i = 0; i < ANOMALY_DETECTORS.length; ++i) ANOMALY_DETECTORS[i].detectorOrdinal = i;
+ }
+
+ // A view from view capture data converted to a form that's convenient for detecting anomalies.
+ static class AnalysisNode {
+ public String className;
+ public String resourceId;
+ public AnalysisNode parent;
+
+ // Window coordinates of the view.
+ public float left;
+ public float top;
+ public float right;
+ public float bottom;
+
+ // Visible scale and alpha, build recursively from the ancestor list.
+ public float scaleX;
+ public float scaleY;
+ public float alpha; // Always > 0
+
+ public int frameN;
+
+ // Timestamp of the frame when this view became abruptly visible, i.e. its alpha became 1
+ // the next frame after it was 0 or the view wasn't visible.
+ // If the view is currently invisible or the last appearance wasn't abrupt, the value is -1.
+ public long timeBecameVisibleNs;
+
+ // Timestamp of the frame when this view became abruptly invisible last time, i.e. its
+ // alpha became 0, or view disappeared, after being 1 in the previous frame.
+ // If the view is currently visible or the last disappearance wasn't abrupt, the value is
+ // -1.
+ public long timeBecameInvisibleNs;
+
+ public ViewNode viewCaptureNode;
+
+ // Class name + resource id
+ public String nodeIdentity;
+
+ // Collection of detector-specific data for this node.
+ public final Object[] detectorsData = new Object[ANOMALY_DETECTORS.length];
+
+ @Override
+ public String toString() {
+ return String.format("view window coordinates: (%s, %s, %s, %s)",
+ left, top, right, bottom);
+ }
+ }
+
+ /**
+ * Scans a view capture record and searches for view animation anomalies. Can find anomalies for
+ * multiple views.
+ * Returns a map from the view path to the anomaly message for the view. Non-empty map means
+ * that anomalies were detected.
+ */
+ public static Map<String, String> getAnomalies(ExportedData viewCaptureData) {
+ final Map<String, String> anomalies = new HashMap<>();
+
+ final int scrimClassIndex = viewCaptureData.getClassnameList().indexOf(SCRIM_VIEW_CLASS);
+
+ final int windowDataCount = viewCaptureData.getWindowDataCount();
+ for (int i = 0; i < windowDataCount; ++i) {
+ analyzeWindowData(
+ viewCaptureData, viewCaptureData.getWindowData(i), scrimClassIndex, anomalies);
+ }
+ return anomalies;
+ }
+
+ private static void analyzeWindowData(ExportedData viewCaptureData, WindowData windowData,
+ int scrimClassIndex, Map<String, String> anomalies) {
+ // View hash code => Last seen node with this hash code.
+ // The view is added when we analyze the first frame where it's visible.
+ // After that, it gets updated for every frame where it's visible.
+ // As we go though frames, if a view becomes invisible, it stays in the map.
+ final Map<Integer, AnalysisNode> lastSeenNodes = new HashMap<>();
+
+ int windowWidthPx = -1;
+ int windowHeightPx = -1;
+
+ for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) {
+ final FrameData frame = windowData.getFrameData(frameN);
+ final ViewNode rootNode = frame.getNode();
+
+ // If the rotation or window size has changed, reset the analyzer state.
+ final boolean isFirstFrame = windowWidthPx != rootNode.getWidth()
+ || windowHeightPx != rootNode.getHeight();
+ if (isFirstFrame) {
+ windowWidthPx = rootNode.getWidth();
+ windowHeightPx = rootNode.getHeight();
+ lastSeenNodes.clear();
+ }
+
+ final int windowSizePx = Math.max(rootNode.getWidth(), rootNode.getHeight());
+
+ analyzeFrame(frameN, isFirstFrame, frame, viewCaptureData, lastSeenNodes,
+ scrimClassIndex, anomalies, windowSizePx);
+ }
+ }
+
+ private static void analyzeFrame(int frameN, boolean isFirstFrame, FrameData frame,
+ ExportedData viewCaptureData,
+ Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex,
+ Map<String, String> anomalies, int windowSizePx) {
+ // Analyze the node tree starting from the root.
+ long frameTimeNs = frame.getTimestamp();
+ analyzeView(
+ frameTimeNs,
+ frame.getNode(),
+ /* parent = */ null,
+ frameN,
+ isFirstFrame,
+ /* leftShift = */ 0,
+ /* topShift = */ 0,
+ viewCaptureData,
+ lastSeenNodes,
+ scrimClassIndex,
+ anomalies,
+ windowSizePx);
+
+ // Analyze transitions when a view visible in the previous frame became invisible in the
+ // current one.
+ for (AnalysisNode info : lastSeenNodes.values()) {
+ if (info.frameN == frameN - 1) {
+ if (!info.viewCaptureNode.getWillNotDraw()) {
+ Arrays.stream(ANOMALY_DETECTORS).forEach(
+ detector ->
+ detectAnomaly(
+ detector,
+ frameN,
+ /* oldInfo = */ info,
+ /* newInfo = */ null,
+ anomalies,
+ frameTimeNs,
+ windowSizePx)
+ );
+ }
+ info.timeBecameInvisibleNs = info.alpha == 1 ? frameTimeNs : -1;
+ info.timeBecameVisibleNs = -1;
+ }
+ }
+ }
+
+ private static void analyzeView(long frameTimeNs, ViewNode viewCaptureNode, AnalysisNode parent,
+ int frameN,
+ boolean isFirstFrame, float leftShift, float topShift, ExportedData viewCaptureData,
+ Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex,
+ Map<String, String> anomalies, int windowSizePx) {
+ // Skip analysis of invisible views
+ final float parentAlpha = parent != null ? parent.alpha : 1;
+ final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha);
+ if (alpha <= 0.0) return;
+
+ // Calculate analysis node parameters
+ final int hashcode = viewCaptureNode.getHashcode();
+ final int classIndex = viewCaptureNode.getClassnameIndex();
+
+ final float parentScaleX = parent != null ? parent.scaleX : 1;
+ final float parentScaleY = parent != null ? parent.scaleY : 1;
+ final float scaleX = parentScaleX * viewCaptureNode.getScaleX();
+ final float scaleY = parentScaleY * viewCaptureNode.getScaleY();
+
+ final float left = leftShift
+ + (viewCaptureNode.getLeft() + viewCaptureNode.getTranslationX()) * parentScaleX
+ + viewCaptureNode.getWidth() * (parentScaleX - scaleX) / 2;
+ final float top = topShift
+ + (viewCaptureNode.getTop() + viewCaptureNode.getTranslationY()) * parentScaleY
+ + viewCaptureNode.getHeight() * (parentScaleY - scaleY) / 2;
+ final float width = viewCaptureNode.getWidth() * scaleX;
+ final float height = viewCaptureNode.getHeight() * scaleY;
+
+ // Initialize new analysis node
+ final AnalysisNode newAnalysisNode = new AnalysisNode();
+ newAnalysisNode.className = viewCaptureData.getClassname(classIndex);
+ newAnalysisNode.resourceId = viewCaptureNode.getId();
+ newAnalysisNode.nodeIdentity =
+ getNodeIdentity(newAnalysisNode.className, newAnalysisNode.resourceId);
+ newAnalysisNode.parent = parent;
+ newAnalysisNode.left = left;
+ newAnalysisNode.top = top;
+ newAnalysisNode.right = left + width;
+ newAnalysisNode.bottom = top + height;
+ newAnalysisNode.scaleX = scaleX;
+ newAnalysisNode.scaleY = scaleY;
+ newAnalysisNode.alpha = alpha;
+ newAnalysisNode.frameN = frameN;
+ newAnalysisNode.timeBecameInvisibleNs = -1;
+ newAnalysisNode.viewCaptureNode = viewCaptureNode;
+ Arrays.stream(ANOMALY_DETECTORS).forEach(
+ detector -> detector.initializeNode(newAnalysisNode));
+
+ final AnalysisNode oldAnalysisNode = lastSeenNodes.get(hashcode); // may be null
+
+ if (oldAnalysisNode != null && oldAnalysisNode.frameN + 1 == frameN) {
+ // If this view was present in the previous frame, keep the time when it became visible.
+ newAnalysisNode.timeBecameVisibleNs = oldAnalysisNode.timeBecameVisibleNs;
+ } else {
+ // If the view is becoming visible after being invisible, initialize the time when it
+ // became visible with a new value.
+ // If the view became visible abruptly, i.e. alpha jumped from 0 to 1 between the
+ // previous and the current frames, then initialize with the time of the current
+ // frame. Otherwise, use -1.
+ newAnalysisNode.timeBecameVisibleNs = newAnalysisNode.alpha >= 1 ? frameTimeNs : -1;
+ }
+
+ // Detect anomalies for the view.
+ if (!isFirstFrame && !viewCaptureNode.getWillNotDraw()) {
+ Arrays.stream(ANOMALY_DETECTORS).forEach(
+ detector ->
+ detectAnomaly(detector, frameN, oldAnalysisNode, newAnalysisNode,
+ anomalies, frameTimeNs, windowSizePx)
+ );
+ }
+ lastSeenNodes.put(hashcode, newAnalysisNode);
+
+ // Enumerate children starting from the topmost one. Stop at ScrimView, if present.
+ final float leftShiftForChildren = left - viewCaptureNode.getScrollX();
+ final float topShiftForChildren = top - viewCaptureNode.getScrollY();
+ for (int i = viewCaptureNode.getChildrenCount() - 1; i >= 0; --i) {
+ final ViewNode child = viewCaptureNode.getChildren(i);
+
+ // Don't analyze anything under scrim view because we don't know whether it's
+ // transparent.
+ if (child.getClassnameIndex() == scrimClassIndex) break;
+
+ analyzeView(frameTimeNs, child, newAnalysisNode, frameN, isFirstFrame,
+ leftShiftForChildren,
+ topShiftForChildren,
+ viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies, windowSizePx);
+ }
+ }
+
+ private static void detectAnomaly(AnomalyDetector detector, int frameN,
+ AnalysisNode oldAnalysisNode, AnalysisNode newAnalysisNode,
+ Map<String, String> anomalies, long frameTimeNs, int windowSizePx) {
+ final String maybeAnomaly =
+ detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs,
+ windowSizePx);
+ if (maybeAnomaly != null) {
+ AnalysisNode latestInfo = newAnalysisNode != null ? newAnalysisNode : oldAnalysisNode;
+ final String viewDiagPath = diagPathFromRoot(latestInfo);
+ if (!anomalies.containsKey(viewDiagPath)) {
+ anomalies.put(viewDiagPath, String.format("%s, %s", maybeAnomaly, latestInfo));
+ }
+ }
+ }
+
+ private static float getVisibleAlpha(ViewNode node, float parenVisibleAlpha) {
+ return node.getVisibility() == VISIBLE
+ ? parenVisibleAlpha * Math.max(0, Math.min(node.getAlpha(), 1))
+ : 0f;
+ }
+
+ private static String classNameToSimpleName(String className) {
+ return className.substring(className.lastIndexOf(".") + 1);
+ }
+
+ private static String diagPathFromRoot(AnalysisNode analysisNode) {
+ final StringBuilder path = new StringBuilder(analysisNode.nodeIdentity);
+ for (AnalysisNode ancestor = analysisNode.parent;
+ ancestor != null;
+ ancestor = ancestor.parent) {
+ path.insert(0, ancestor.nodeIdentity + "|");
+ }
+ return path.toString();
+ }
+
+ private static String getNodeIdentity(String className, String resourceId) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(classNameToSimpleName(className));
+ if (!"NO_ID".equals(resourceId)) sb.append(":" + resourceId);
+ return sb.toString();
+ }
+}
diff --git a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
deleted file mode 100644
index 9cd0a2e..0000000
--- a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.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(3)
- assertThat(workspaceSpecs.workspaceHeightSpecList[0].toString())
- .isEqualTo(
- "WorkspaceSpec(" +
- "maxAvailableSize=1533, " +
- "specType=HEIGHT, " +
- "startPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "endPadding=SizeSpec(fixedSize=84.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "gutter=SizeSpec(fixedSize=42.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "cellSize=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.15808, " +
- "ofRemainderSpace=0.0)" +
- ")"
- )
- assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
- .isEqualTo(
- "WorkspaceSpec(" +
- "maxAvailableSize=1607, " +
- "specType=HEIGHT, " +
- "startPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "endPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=1.0), " +
- "gutter=SizeSpec(fixedSize=42.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "cellSize=SizeSpec(fixedSize=273.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0)" +
- ")"
- )
- assertThat(workspaceSpecs.workspaceHeightSpecList[2].toString())
- .isEqualTo(
- "WorkspaceSpec(" +
- "maxAvailableSize=26247, " +
- "specType=HEIGHT, " +
- "startPadding=SizeSpec(fixedSize=21.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "endPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=1.0), " +
- "gutter=SizeSpec(fixedSize=42.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "cellSize=SizeSpec(fixedSize=273.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0)" +
- ")"
- )
- assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
- assertThat(workspaceSpecs.workspaceWidthSpecList[0].toString())
- .isEqualTo(
- "WorkspaceSpec(" +
- "maxAvailableSize=26247, " +
- "specType=WIDTH, " +
- "startPadding=SizeSpec(fixedSize=58.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "endPadding=SizeSpec(fixedSize=58.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "gutter=SizeSpec(fixedSize=42.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
- "cellSize=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.25)" +
- ")"
- )
- }
-
- @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))
- }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
index 10afe13..425c3c0 100644
--- a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -45,8 +45,8 @@
mLauncher.clickObject(
mLauncher.waitForObjectInContainer(
mWidgetCell.getParent().getParent().getParent().getParent(),
- By.text(ADD_AUTOMATICALLY)),
- LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER);
+ By.text(ADD_AUTOMATICALLY))
+ );
mLauncher.waitUntilLauncherObjectGone(getSelector());
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 399abc7..fb08ea4 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -210,6 +210,9 @@
public AppIcon getAppIcon(String appName) {
AppIcon appIcon = tryGetAppIcon(appName);
mLauncher.assertNotNull("Unable to scroll to a clickable icon: " + appName, appIcon);
+ // appIcon.getAppName() checks for content description, so it is possible that it can have
+ // trailing words. So check if the content description contains the appName.
+ mLauncher.assertTrue("Wrong app icon name.", appIcon.getAppName().contains(appName));
return appIcon;
}
@@ -333,4 +336,11 @@
final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
return testInfo == null ? 0 : testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+
+ /**
+ * Return the QSB UI object on the AllApps screen.
+ * @return the QSB UI object.
+ */
+ @NonNull
+ public abstract Qsb getQsb();
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java
index c4744a1..0e0291f 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java
@@ -62,4 +62,10 @@
return mLauncher.getTestInfo(TestProtocol.REQUEST_TASKBAR_APPS_LIST_SCROLL_Y)
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+
+ @NonNull
+ @Override
+ public TaskbarAllAppsQsb getQsb() {
+ return new TaskbarAllAppsQsb(mLauncher, verifyActiveContainer());
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsQsb.java b/tests/tapl/com/android/launcher3/tapl/AllAppsQsb.java
index 0931cd4..1692351 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsQsb.java
@@ -22,16 +22,7 @@
*/
class AllAppsQsb extends Qsb {
- private final UiObject2 mAllAppsContainer;
-
AllAppsQsb(LauncherInstrumentation launcher, UiObject2 allAppsContainer) {
- super(launcher);
- mAllAppsContainer = allAppsContainer;
- waitForQsbObject();
- }
-
- @Override
- protected UiObject2 waitForQsbObject() {
- return mLauncher.waitForObjectInContainer(mAllAppsContainer, "search_container_all_apps");
+ super(launcher, allAppsContainer, "search_container_all_apps");
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 0a0cf07..85098c8 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -37,10 +37,14 @@
}
static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
- return By.clazz(TextView.class).textContains(appName)
+ return By.clazz(TextView.class).desc(makeMultilinePattern(appName))
.pkg(launcher.getLauncherPackageName());
}
+ static BySelector getMenuItemSelector(String text, LauncherInstrumentation launcher) {
+ return By.clazz(TextView.class).text(text).pkg(launcher.getLauncherPackageName());
+ }
+
static BySelector getAnyAppIconSelector() {
return By.clazz(TextView.class);
}
@@ -94,4 +98,24 @@
public String getIconName() {
return getObject().getText();
}
+
+ /**
+ * Return the app name of a icon by the content description. This should be used when trying to
+ * get the name of an app where the text of it is multiline.
+ */
+ @NonNull
+ String getAppName() {
+ return getObject().getContentDescription();
+ }
+
+ /**
+ * Create a regular expression pattern that matches strings starting with the app name, where
+ * spaces in the app name are replaced with zero or more occurrences of the "\s" character
+ * (which represents a whitespace character in regular expressions), followed by any characters
+ * after the app name.
+ */
+ static Pattern makeMultilinePattern(String appName) {
+ return Pattern.compile(appName.replaceAll("\\s+", "\\\\s*") + ".*",
+ Pattern.DOTALL);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index 667290f..bbcc6a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -50,7 +50,7 @@
*/
public AppIconMenuItem getMenuItem(String shortcutText) {
final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
- AppIcon.getAppIconSelector(shortcutText, mLauncher));
+ AppIcon.getMenuItemSelector(shortcutText, mLauncher));
return createMenuItem(menuItem);
}
@@ -59,7 +59,7 @@
*/
public SplitScreenMenuItem getSplitScreenMenuItem() {
final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
- AppIcon.getAppIconSelector("Split screen", mLauncher));
+ AppIcon.getMenuItemSelector("Split screen", mLauncher));
return new SplitScreenMenuItem(mLauncher, menuItem);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 5a96d95..677f204 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -28,6 +28,8 @@
import androidx.annotation.NonNull;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
+import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
import com.android.launcher3.testing.shared.TestProtocol;
import java.util.List;
@@ -64,85 +66,71 @@
}
- protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
- return mLauncher.isTablet();
- }
-
protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
return false;
}
protected void goToOverviewUnchecked() {
- switch (mLauncher.getNavigationModel()) {
- case ZERO_BUTTON: {
- final long downTime = SystemClock.uptimeMillis();
- sendDownPointerToEnterOverviewToLauncher(downTime);
- String swipeAndHoldToEnterOverviewActionName =
- "swiping and holding to enter overview";
- // If swiping from an app (e.g. Overview is in Background), we pause and hold on
- // swipe up to make overview appear, or else swiping without holding would take
- // us to the Home state. If swiping up from Home (e.g. Overview in Home or
- // Workspace state where the below condition is true), there is no need to pause,
- // and we will not test for an intermediate carousel as one will not exist.
- if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
- mLauncher.runToState(
- () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
- OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
- sendUpPointerToEnterOverviewToLauncher(downTime);
- } else {
- // If swiping up from an app to overview, pause on intermediate carousel
- // until snapshots are visible. No intermediate carousel when swiping from
- // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
- // only a single task exists, no snapshots will be available during swipe up.
- mLauncher.executeAndWaitForLauncherEvent(
- () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
- event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
- event.getClassName().toString()),
- () -> "Pause wasn't detected",
- swipeAndHoldToEnterOverviewActionName);
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "paused on swipe up to overview")) {
- if (mLauncher.getRecentTasks().size() > 1) {
- // When swiping up to grid-overview for tablets, the swiped tab will be
- // in the middle of the screen (TaskViewSimulator, not a snapshot), and
- // all remaining snapshots will be to the left of that task. In
- // non-tablet overview, snapshots can be on either side of the swiped
- // task, but we still check that they become visible after swiping and
- // pausing.
- mLauncher.waitForOverviewObject("snapshot");
- if (mLauncher.isTablet()) {
- List<UiObject2> tasks = mLauncher.getDevice().findObjects(
- mLauncher.getOverviewObjectSelector("snapshot"));
- final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
- mLauncher.assertTrue(
- "All tasks not to the left of the swiped task",
- tasks.stream()
- .allMatch(
- t -> t.getVisibleBounds().right < centerX));
- }
-
- }
- String upPointerToEnterOverviewActionName =
- "sending UP pointer to enter overview";
- mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime),
- OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
- }
- }
- break;
- }
-
- case THREE_BUTTON:
- if (mLauncher.isTablet()) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_TOUCH_DOWN);
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_TOUCH_UP);
- }
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+ if (mLauncher.getNavigationModel() == NavigationModel.ZERO_BUTTON
+ || mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER) {
+ final long downTime = SystemClock.uptimeMillis();
+ sendDownPointerToEnterOverviewToLauncher(downTime);
+ String swipeAndHoldToEnterOverviewActionName =
+ "swiping and holding to enter overview";
+ // If swiping from an app (e.g. Overview is in Background), we pause and hold on
+ // swipe up to make overview appear, or else swiping without holding would take
+ // us to the Home state. If swiping up from Home (e.g. Overview in Home or
+ // Workspace state where the below condition is true), there is no need to pause,
+ // and we will not test for an intermediate carousel as one will not exist.
+ if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
mLauncher.runToState(
- () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
- OVERVIEW_STATE_ORDINAL, "clicking Recents button");
- break;
+ () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
+ OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
+ sendUpPointerToEnterOverviewToLauncher(downTime);
+ } else {
+ // If swiping up from an app to overview, pause on intermediate carousel
+ // until snapshots are visible. No intermediate carousel when swiping from
+ // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
+ // only a single task exists, no snapshots will be available during swipe up.
+ mLauncher.executeAndWaitForLauncherEvent(
+ () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
+ event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
+ event.getClassName().toString()),
+ () -> "Pause wasn't detected",
+ swipeAndHoldToEnterOverviewActionName);
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "paused on swipe up to overview")) {
+ if (mLauncher.getRecentTasks().size() > 1) {
+ // When swiping up to grid-overview for tablets, the swiped tab will be
+ // in the middle of the screen (TaskViewSimulator, not a snapshot), and
+ // all remaining snapshots will be to the left of that task. In
+ // non-tablet overview, snapshots can be on either side of the swiped
+ // task, but we still check that they become visible after swiping and
+ // pausing.
+ mLauncher.waitForOverviewObject("snapshot");
+ if (mLauncher.isTablet()) {
+ List<UiObject2> tasks = mLauncher.getDevice().findObjects(
+ mLauncher.getOverviewObjectSelector("snapshot"));
+ final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+ mLauncher.assertTrue(
+ "All tasks not to the left of the swiped task",
+ tasks.stream()
+ .allMatch(
+ t -> t.getVisibleBounds().right < centerX));
+ }
+
+ }
+ String upPointerToEnterOverviewActionName =
+ "sending UP pointer to enter overview";
+ mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime),
+ OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
+ }
+ }
+ } else {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+ mLauncher.runToState(
+ () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
+ OVERVIEW_STATE_ORDINAL, "clicking Recents button");
}
expectSwitchToOverviewEvents();
}
@@ -154,12 +142,9 @@
final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
final int startY = getSwipeStartY();
final Point start = new Point(centerX, startY);
- final LauncherInstrumentation.GestureScope gestureScope =
- zeroButtonToOverviewGestureStartsInLauncher()
- ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
- : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
- mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start,
+ LauncherInstrumentation.GestureScope.EXPECT_PILFER);
if (!mLauncher.isLauncher3()) {
mLauncher.expectEvent(TestProtocol.SEQUENCE_PILFER,
@@ -175,10 +160,6 @@
final Point start = new Point(centerX, startY);
final Point end =
new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
- final LauncherInstrumentation.GestureScope gestureScope =
- zeroButtonToOverviewGestureStartsInLauncher()
- ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
- : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
mLauncher.movePointer(
downTime,
@@ -186,7 +167,7 @@
ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
start,
end,
- gestureScope);
+ LauncherInstrumentation.GestureScope.EXPECT_PILFER);
}
private void sendUpPointerToEnterOverviewToLauncher(long downTime) {
@@ -197,13 +178,9 @@
final Point end =
new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
- final LauncherInstrumentation.GestureScope gestureScope =
- zeroButtonToOverviewGestureStartsInLauncher()
- ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
- : LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER;
-
mLauncher.sendPointer(downTime, SystemClock.uptimeMillis(),
- MotionEvent.ACTION_UP, end, gestureScope);
+ MotionEvent.ACTION_UP, end,
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
}
/**
@@ -235,71 +212,48 @@
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to quick switch to the previous app")) {
verifyActiveContainer();
- final boolean launcherWasVisible = mLauncher.isLauncherVisible();
- switch (mLauncher.getNavigationModel()) {
- case ZERO_BUTTON: {
- final int startX;
- final int startY;
- final int endX;
- final int endY;
- final int cornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
- if (toRight) {
- // Swipe from the bottom left to the bottom right of the screen.
- startX = cornerRadius;
- startY = getSwipeStartY();
- endX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
- endY = startY;
- } else {
- // Swipe from the bottom right to the bottom left of the screen.
- startX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
- startY = getSwipeStartY();
- endX = cornerRadius;
- endY = startY;
- }
-
- final boolean isZeroButton = mLauncher.getNavigationModel()
- == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
- LauncherInstrumentation.GestureScope gestureScope =
- launcherWasVisible && isZeroButton
- ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
- : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
- mLauncher.executeAndWaitForEvent(
- () -> mLauncher.linearGesture(
- startX, startY, endX, endY, 20, false, gestureScope),
- event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Quick switch gesture didn't change window state", "swiping");
- break;
+ if (mLauncher.getNavigationModel() == NavigationModel.ZERO_BUTTON
+ || mLauncher.getTrackpadGestureType() == TrackpadGestureType.FOUR_FINGER) {
+ final int startX;
+ final int startY;
+ final int endX;
+ final int endY;
+ final int cornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
+ if (toRight) {
+ // Swipe from the bottom left to the bottom right of the screen.
+ startX = cornerRadius;
+ startY = getSwipeStartY();
+ endX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
+ endY = startY;
+ } else {
+ // Swipe from the bottom right to the bottom left of the screen.
+ startX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
+ startY = getSwipeStartY();
+ endX = cornerRadius;
+ endY = startY;
}
- case THREE_BUTTON:
- // Double press the recents button.
- UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
- if (mLauncher.isTablet()) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_TOUCH_DOWN);
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_TOUCH_UP);
- }
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
- mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL,
- "clicking Recents button for the first time");
- mLauncher.getOverview();
- if (mLauncher.isTablet()) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_TOUCH_DOWN);
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
- LauncherInstrumentation.EVENT_TOUCH_UP);
- }
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
- mLauncher.executeAndWaitForEvent(
- () -> recentsButton.click(),
- event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Pressing recents button didn't change window state",
- "clicking Recents button for the second time");
- break;
+ mLauncher.executeAndWaitForEvent(
+ () -> mLauncher.linearGesture(
+ startX, startY, endX, endY, 20, false,
+ LauncherInstrumentation.GestureScope.EXPECT_PILFER),
+ event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+ () -> "Quick switch gesture didn't change window state", "swiping");
+ } else {
+ // Double press the recents button.
+ UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+ mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL,
+ "clicking Recents button for the first time");
+ mLauncher.getOverview();
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+ mLauncher.executeAndWaitForEvent(
+ () -> recentsButton.click(),
+ event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+ () -> "Pressing recents button didn't change window state",
+ "clicking Recents button for the second time");
}
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
- return;
}
}
@@ -312,6 +266,8 @@
}
protected int getSwipeStartY() {
- return mLauncher.getRealDisplaySize().y - 1;
+ return mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER
+ ? mLauncher.getDevice().getDisplayHeight() * 3 / 4
+ : mLauncher.getRealDisplaySize().y - 1;
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 2c3c028..aa5c770 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -324,13 +324,16 @@
if (!hasTasks() || isClearAllVisible()) {
return false;
}
- OverviewTask task = mLauncher.isTablet() ? getFocusedTaskForTablet() : getCurrentTask();
+ boolean isTablet = mLauncher.isTablet();
+ if (isTablet && mLauncher.isGridOnlyOverviewEnabled()) {
+ return false;
+ }
+ OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
if (task == null) {
return false;
}
// In tablets, if focused task is not in center, overview actions aren't visible.
- if (mLauncher.isTablet()
- && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
+ if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
return false;
}
// Overview actions aren't visible for split screen tasks.
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index ee9dd1a..252435b 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -59,11 +59,6 @@
}
@Override
- protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
- return true;
- }
-
- @Override
protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
return true;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index 1239d7a..2951901 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -30,23 +30,28 @@
}
/**
- * Swipes down to Workspace.
+ * Swipes up or down to dismiss to Workspace.
+ * @param swipeDown Swipe all apps down to dismiss, otherwise swipe up to dismiss by going home.
*
* @return the Workspace object.
*/
@NonNull
- public Workspace switchToWorkspace() {
+ public Workspace switchToWorkspace(boolean swipeDown) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to switch from all apps to workspace")) {
UiObject2 allAppsContainer = verifyActiveContainer();
final int startX = allAppsContainer.getVisibleCenter().x;
- final int startY = getTopVisibleIconBounds(allAppsContainer).centerY();
- final int endY = mLauncher.getDevice().getDisplayHeight();
+ final int startY = swipeDown ? getTopVisibleIconBounds(allAppsContainer).centerY()
+ : mLauncher.getDevice().getDisplayHeight();
+ final int endY =
+ swipeDown ? mLauncher.getDevice().getDisplayHeight() : getTopVisibleIconBounds(
+ allAppsContainer).centerY();
LauncherInstrumentation.log(
"switchToWorkspace: startY = " + startY + ", endY = " + endY
- + ", slop = " + mLauncher.getTouchSlop());
+ + ", slop = " + mLauncher.getTouchSlop()
+ + ", swipeDown = " + swipeDown);
mLauncher.swipeToState(
startX,
@@ -54,7 +59,9 @@
startX,
endY,
5 /* steps */,
- NORMAL_STATE_ORDINAL, LauncherInstrumentation.GestureScope.INSIDE);
+ NORMAL_STATE_ORDINAL,
+ swipeDown ? LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER
+ : LauncherInstrumentation.GestureScope.EXPECT_PILFER);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"swiped to workspace")) {
@@ -110,11 +117,8 @@
}
}
- /**
- * Return the QSB UI object on the AllApps screen.
- * @return the QSB UI object.
- */
@NonNull
+ @Override
public Qsb getQsb() {
return new AllAppsQsb(mLauncher, verifyActiveContainer());
}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
index 20d09a1..5385c65 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
@@ -22,16 +22,7 @@
*/
class HomeQsb extends Qsb {
- private final UiObject2 mHotSeat;
-
HomeQsb(LauncherInstrumentation launcher, UiObject2 hotseat) {
- super(launcher);
- mHotSeat = hotseat;
- waitForQsbObject();
- }
-
- @Override
- protected UiObject2 waitForQsbObject() {
- return mLauncher.waitForObjectInContainer(mHotSeat, "search_container_hotseat");
+ super(launcher, hotseat, "search_container_hotseat");
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 48e327f..a953fa9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -152,7 +152,7 @@
downTime,
MotionEvent.ACTION_DOWN,
iconCenter,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
LauncherInstrumentation.log("movePointerForStartDrag: sent down");
expectLongClickEvents.run();
waitForLongPressConfirmation();
@@ -165,7 +165,7 @@
downTime,
downTime,
/* slowDown= */ true,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
}
private int getStartDragThreshold() {
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index a59eff7..30417c0 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -30,6 +30,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -37,6 +38,7 @@
import androidx.test.uiautomator.Condition;
import androidx.test.uiautomator.UiDevice;
+import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.testing.shared.TestProtocol;
/**
@@ -50,6 +52,8 @@
// UNSTASHED_TASKBAR_HANDLE_HINT_SCALE value from TaskbarStashController.
private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
+ private static final int STASHED_TASKBAR_BOTTOM_EDGE_DP = 1;
+
private final Condition<UiDevice, Boolean> mStashedTaskbarHintScaleCondition =
device -> mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat(
TestProtocol.TEST_INFO_RESPONSE_FIELD) - UNSTASHED_TASKBAR_HANDLE_HINT_SCALE
@@ -124,13 +128,13 @@
mLauncher.getRealDisplaySize().x / 2, unstashTargetY);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, unstashTarget,
- LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
+ LauncherInstrumentation.GestureScope.EXPECT_PILFER);
LauncherInstrumentation.log("showTaskbar: sent down");
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("pressed down")) {
mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, unstashTarget,
- LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
+ LauncherInstrumentation.GestureScope.EXPECT_PILFER);
return new Taskbar(mLauncher);
}
@@ -179,7 +183,7 @@
downTime,
SystemClock.uptimeMillis(),
/* slowDown= */ false,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
try (LauncherInstrumentation.Closable c3 = launcher.addContextLayer(
"moved pointer to drop point")) {
@@ -190,7 +194,7 @@
SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP,
endPoint,
- LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
+ "after drop");
@@ -209,7 +213,7 @@
*
* <p>This unstashing occurs when not actively hovering the taskbar.
*/
- public void hoverScreenBottomEdgeToUnstashTaskbar() {
+ public Taskbar hoverScreenBottomEdgeToUnstashTaskbar() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"cursor hover entering screen edge to unstash taskbar")) {
@@ -220,19 +224,23 @@
int leftEdge = 10;
Point taskbarUnstashArea = new Point(leftEdge, mLauncher.getRealDisplaySize().y - 1);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
- new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null);
+ new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null,
+ InputDevice.SOURCE_MOUSE);
mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
- new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null);
+ new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null,
+ InputDevice.SOURCE_MOUSE);
+
+ return new Taskbar(mLauncher);
}
}
/**
* Emulate the cursor hovering the taskbar to get unstash hint, then hovering below to unstash.
*/
- public void hoverBelowHintedTaskbarToUnstash() {
+ public Taskbar hoverBelowHintedTaskbarToUnstash() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"cursor hover entering stashed taskbar")) {
@@ -240,7 +248,8 @@
Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2,
mLauncher.getRealDisplaySize().y - 1);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
- new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null);
+ new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null,
+ InputDevice.SOURCE_MOUSE);
mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition,
LauncherInstrumentation.WAIT_TIME_MS);
@@ -251,9 +260,11 @@
Point taskbarUnstashArea = new Point(mLauncher.getRealDisplaySize().x / 2,
mLauncher.getRealDisplaySize().y - 1);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
- new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null);
+ new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null,
+ InputDevice.SOURCE_MOUSE);
mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID);
+ return new Taskbar(mLauncher);
}
}
}
@@ -288,4 +299,48 @@
}
}
}
+
+ /**
+ * Emulate the cursor clicking the stashed taskbar to go home.
+ */
+ public Workspace clickStashedTaskbarToGoHome() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "cursor hover entering stashed taskbar")) {
+ long downTime = SystemClock.uptimeMillis();
+ int stashedTaskbarBottomEdge = ResourceUtils.pxFromDp(STASHED_TASKBAR_BOTTOM_EDGE_DP,
+ mLauncher.getResources().getDisplayMetrics());
+ Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2,
+ mLauncher.getRealDisplaySize().y - stashedTaskbarBottomEdge - 1);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
+ new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null,
+ InputDevice.SOURCE_MOUSE);
+
+ mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition,
+ LauncherInstrumentation.WAIT_TIME_MS);
+
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "cursor clicking stashed taskbar to go home")) {
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
+ new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
+ null, InputDevice.SOURCE_MOUSE);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
+ new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER,
+ InputDevice.SOURCE_MOUSE);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS,
+ new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
+ null, InputDevice.SOURCE_MOUSE);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE,
+ new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
+ null, InputDevice.SOURCE_MOUSE);
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
+ new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER,
+ InputDevice.SOURCE_MOUSE);
+
+ return mLauncher.getWorkspace();
+ }
+ }
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index c4f5da5..c58ae16 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.DONT_KILL_APP;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
@@ -98,18 +99,9 @@
private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15;
private static final int GESTURE_STEP_MS = 16;
- static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
- static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
- private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
static final Pattern EVENT_START = Pattern.compile("start:");
- static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
- static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
- static final Pattern EVENT_TOUCH_CANCEL_TIS = getTouchEventPatternTIS("ACTION_CANCEL");
- static final Pattern EVENT_HOVER_ENTER_TIS = getTouchEventPatternTIS("ACTION_HOVER_ENTER");
- static final Pattern EVENT_HOVER_EXIT_TIS = getTouchEventPatternTIS("ACTION_HOVER_EXIT");
-
private static final Pattern EVENT_KEY_BACK_DOWN =
getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
private static final Pattern EVENT_KEY_BACK_UP =
@@ -129,26 +121,19 @@
public enum NavigationModel {ZERO_BUTTON, THREE_BUTTON}
- // Where the gesture happens: outside of Launcher, inside or from inside to outside and
- // whether the gesture recognition triggers pilfer.
+ // Defines whether the gesture recognition triggers pilfer.
public enum GestureScope {
- OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE,
- INSIDE_TO_OUTSIDE_WITHOUT_PILFER,
- INSIDE_TO_OUTSIDE_WITH_KEYCODE, // For gestures that will trigger a keycode from TIS.
- OUTSIDE_WITH_KEYCODE,
+ DONT_EXPECT_PILFER,
+ EXPECT_PILFER,
}
- /**
- * Represents a point in the code at which a callback can run.
- */
- public enum CALLBACK_RUN_POINT {
- CALLBACK_HOLD_BEFORE_DROP,
- CALLBACK_HOVER_ENTER,
- CALLBACK_HOVER_EXIT,
+ public enum TrackpadGestureType {
+ NONE,
+ TWO_FINGER,
+ THREE_FINGER,
+ FOUR_FINGER
}
- private Consumer<CALLBACK_RUN_POINT> mCallbackAtRunPoint = null;
-
// Base class for launcher containers.
abstract static class VisibleContainer {
protected final LauncherInstrumentation mLauncher;
@@ -176,7 +161,7 @@
void close();
}
- private static final String WORKSPACE_RES_ID = "workspace";
+ static final String WORKSPACE_RES_ID = "workspace";
private static final String APPS_RES_ID = "apps_view";
private static final String OVERVIEW_RES_ID = "overview_panel";
private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
@@ -208,20 +193,8 @@
private boolean mCheckEventsForSuccessfulGestures = false;
private Runnable mOnLauncherCrashed;
- private static Pattern getTouchEventPattern(String prefix, String action) {
- // The pattern includes checks that we don't get a multi-touch events or other surprises.
- return Pattern.compile(
- prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
- + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
- }
-
- private static Pattern getTouchEventPattern(String action) {
- return getTouchEventPattern("Touch event", action);
- }
-
- private static Pattern getTouchEventPatternTIS(String action) {
- return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
- }
+ private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
+ private int mPointerCount = 0;
private static Pattern getKeyEventPattern(String action, String keyCode) {
return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
@@ -715,6 +688,19 @@
}
/**
+ * Set the trackpad gesture type of the interaction.
+ * @param trackpadGestureType whether it's not from trackpad, two-finger, three-finger, or
+ * four-finger gesture.
+ */
+ public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
+ mTrackpadGestureType = trackpadGestureType;
+ }
+
+ TrackpadGestureType getTrackpadGestureType() {
+ return mTrackpadGestureType;
+ }
+
+ /**
* Sets expected rotation.
* TAPL periodically checks that Launcher didn't suddenly change the rotation to unexpected one.
* Null parameter disables checks. The initial state is "no checks".
@@ -957,8 +943,8 @@
GestureScope gestureScope = gestureStartFromLauncher
// Without the navigation bar layer, the gesture scope on tablets remains inside the
// launcher process.
- ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
- : GestureScope.OUTSIDE_WITH_PILFER;
+ ? (isTablet() ? GestureScope.DONT_EXPECT_PILFER : GestureScope.EXPECT_PILFER)
+ : GestureScope.EXPECT_PILFER;
linearGesture(
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
@@ -1003,8 +989,11 @@
// We need waiting for any accessibility event generated after pressing Home because
// otherwise waitForIdle may return immediately in case when there was a big enough
// pause in accessibility events prior to pressing Home.
+ boolean isThreeFingerTrackpadGesture =
+ mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
final String action;
- if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+ if (getNavigationModel() == NavigationModel.ZERO_BUTTON
+ || isThreeFingerTrackpadGesture) {
checkForAnomaly(false, true);
final Point displaySize = getRealDisplaySize();
@@ -1020,21 +1009,19 @@
} else {
action = "swiping up to home";
+ int startY = isThreeFingerTrackpadGesture ? displaySize.y * 3 / 4
+ : displaySize.y - 1;
+ int endY = isThreeFingerTrackpadGesture ? displaySize.y / 4 : displaySize.y / 2;
swipeToState(
- displaySize.x / 2, displaySize.y - 1,
- displaySize.x / 2, displaySize.y / 2,
+ displaySize.x / 2, startY,
+ displaySize.x / 2, endY,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
- gestureStartFromLauncher ? GestureScope.INSIDE_TO_OUTSIDE
- : GestureScope.OUTSIDE_WITH_PILFER);
+ GestureScope.EXPECT_PILFER);
}
} else {
log("Hierarchy before clicking home:");
dumpViewHierarchy();
action = "clicking home button";
- if (isTablet()) {
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
- }
runToState(
waitForNavigationUiObject("home")::click,
@@ -1059,21 +1046,19 @@
waitForLauncherInitialized();
final boolean launcherVisible =
isTablet() ? isLauncherContainerVisible() : isLauncherVisible();
- if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+ boolean isThreeFingerTrackpadGesture =
+ mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
+ if (getNavigationModel() == NavigationModel.ZERO_BUTTON
+ || isThreeFingerTrackpadGesture) {
final Point displaySize = getRealDisplaySize();
- final GestureScope gestureScope =
- launcherVisible ? GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
- : GestureScope.OUTSIDE_WITH_KEYCODE;
// TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the
// issue is solved.
- linearGesture(0, displaySize.y / 4, displaySize.x / 2, displaySize.y / 4,
- 10, false, gestureScope);
+ int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0;
+ int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2;
+ linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4,
+ 10, false, GestureScope.DONT_EXPECT_PILFER);
} else {
waitForNavigationUiObject("back").click();
- if (isTablet()) {
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
- }
}
if (launcherVisible) {
if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) {
@@ -1481,15 +1466,17 @@
* animations because in some scenarios there is a playing animations when the click is
* attempted.
*/
- void clickObject(UiObject2 uiObject, GestureScope gestureScope) {
+ void clickObject(UiObject2 uiObject) {
final long clickTime = SystemClock.uptimeMillis();
final Point center = uiObject.getVisibleCenter();
- sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center, gestureScope);
- sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center, gestureScope);
+ sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center,
+ GestureScope.DONT_EXPECT_PILFER);
+ sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center,
+ GestureScope.DONT_EXPECT_PILFER);
}
void clickLauncherObject(UiObject2 object) {
- clickObject(object, GestureScope.INSIDE);
+ clickObject(object);
}
void scrollToLastVisibleRow(
@@ -1582,7 +1569,8 @@
executeAndWaitForLauncherEvent(
() -> linearGesture(
- startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE),
+ startX, startY, endX, endY, steps, slowDown,
+ GestureScope.DONT_EXPECT_PILFER),
event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
() -> "Didn't receive a scroll end message: " + startX + ", " + startY
+ ", " + endX + ", " + endY,
@@ -1592,18 +1580,43 @@
// Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
// fixed interval each time.
public void linearGesture(int startX, int startY, int endX, int endY, int steps,
- boolean slowDown,
- GestureScope gestureScope) {
+ boolean slowDown, GestureScope gestureScope) {
log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
final long downTime = SystemClock.uptimeMillis();
final Point start = new Point(startX, startY);
final Point end = new Point(endX, endY);
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
+ if (mTrackpadGestureType != TrackpadGestureType.NONE) {
+ sendPointer(downTime, downTime, getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 1),
+ start, gestureScope);
+ if (mTrackpadGestureType == TrackpadGestureType.THREE_FINGER
+ || mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) {
+ sendPointer(downTime, downTime,
+ getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 2),
+ start, gestureScope);
+ if (mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) {
+ sendPointer(downTime, downTime,
+ getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 3),
+ start, gestureScope);
+ }
+ }
+ }
final long endTime = movePointer(
start, end, steps, false, downTime, downTime, slowDown, gestureScope);
+ if (mTrackpadGestureType != TrackpadGestureType.NONE) {
+ for (int i = mPointerCount; i >= 2; i--) {
+ sendPointer(downTime, downTime,
+ getPointerAction(MotionEvent.ACTION_POINTER_UP, i - 1),
+ start, gestureScope);
+ }
+ }
sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope);
}
+ private static int getPointerAction(int action, int index) {
+ return action + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+ }
+
long movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime,
long startTime, boolean slowDown, GestureScope gestureScope) {
long endTime = movePointer(downTime, startTime, steps * GESTURE_STEP_MS,
@@ -1627,74 +1640,104 @@
return getContext().getResources();
}
- private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
- float x, float y) {
- MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
- properties.id = 0;
- properties.toolType = Configurator.getInstance().getToolType();
+ private static MotionEvent getTrackpadMotionEvent(long downTime, long eventTime,
+ int action, float x, float y, int pointerCount, TrackpadGestureType gestureType) {
+ MotionEvent.PointerProperties[] pointerProperties =
+ new MotionEvent.PointerProperties[pointerCount];
+ MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
+ boolean isMultiFingerGesture = gestureType != TrackpadGestureType.TWO_FINGER;
+ for (int i = 0; i < pointerCount; i++) {
+ pointerProperties[i] = getPointerProperties(i);
+ pointerCoords[i] = getPointerCoords(x, y);
+ if (isMultiFingerGesture) {
+ pointerCoords[i].setAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT,
+ gestureType == TrackpadGestureType.THREE_FINGER ? 3 : 4);
+ }
+ }
+ return MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerProperties,
+ pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0,
+ InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_CLASS_POINTER, 0, 0,
+ isMultiFingerGesture ? MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
+ : MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE);
+ }
+ private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
+ float x, float y, int source) {
+ return MotionEvent.obtain(downTime, eventTime, action, 1,
+ new MotionEvent.PointerProperties[]{getPointerProperties(0)},
+ new MotionEvent.PointerCoords[]{getPointerCoords(x, y)},
+ 0, 0, 1.0f, 1.0f, 0, 0, source, 0);
+ }
+
+ private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
+ MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+ properties.id = pointerId;
+ properties.toolType = Configurator.getInstance().getToolType();
+ return properties;
+ }
+
+ private static MotionEvent.PointerCoords getPointerCoords(float x, float y) {
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.pressure = 1;
coords.size = 1;
coords.x = x;
coords.y = y;
-
- return MotionEvent.obtain(downTime, eventTime, action, 1,
- new MotionEvent.PointerProperties[]{properties},
- new MotionEvent.PointerCoords[]{coords},
- 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+ return coords;
}
private boolean hasTIS() {
- return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean(TestProtocol.REQUEST_HAS_TIS);
+ return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ boolean isGridOnlyOverviewEnabled() {
+ return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW).getBoolean(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
public void sendPointer(long downTime, long currentTime, int action, Point point,
GestureScope gestureScope) {
+ sendPointer(downTime, currentTime, action, point, gestureScope,
+ InputDevice.SOURCE_TOUCHSCREEN);
+ }
+
+ public void sendPointer(long downTime, long currentTime, int action, Point point,
+ GestureScope gestureScope, int source) {
final boolean hasTIS = hasTIS();
- switch (action) {
+ int pointerCount = mPointerCount;
+
+ boolean isTrackpadGesture = mTrackpadGestureType != TrackpadGestureType.NONE;
+ switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
- if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
- && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
- && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
- expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
- }
- if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) {
- expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
+ if (isTrackpadGesture) {
+ mPointerCount = 1;
+ pointerCount = mPointerCount;
}
break;
case MotionEvent.ACTION_UP:
- if (hasTIS && gestureScope != GestureScope.INSIDE
- && gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
- && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
- || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
+ if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) {
expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
}
- if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
- && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
- && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
- expectEvent(TestProtocol.SEQUENCE_MAIN,
- gestureScope == GestureScope.INSIDE
- || gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER
- ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
- }
- if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) {
- expectEvent(TestProtocol.SEQUENCE_TIS,
- gestureScope == GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
- || gestureScope == GestureScope.OUTSIDE_WITH_KEYCODE
- ? EVENT_TOUCH_CANCEL_TIS : EVENT_TOUCH_UP_TIS);
- }
break;
- case MotionEvent.ACTION_HOVER_ENTER:
- expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_HOVER_ENTER_TIS);
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mPointerCount++;
+ pointerCount = mPointerCount;
break;
- case MotionEvent.ACTION_HOVER_EXIT:
- expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_HOVER_EXIT_TIS);
+ case MotionEvent.ACTION_POINTER_UP:
+ // When the gesture is handled outside, it's cancelled within launcher.
+ mPointerCount--;
break;
}
- final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
+ final MotionEvent event = isTrackpadGesture
+ ? getTrackpadMotionEvent(
+ downTime, currentTime, action, point.x, point.y, pointerCount,
+ mTrackpadGestureType)
+ : getMotionEvent(downTime, currentTime, action, point.x, point.y, source);
+ if (action == MotionEvent.ACTION_BUTTON_PRESS
+ || action == MotionEvent.ACTION_BUTTON_RELEASE) {
+ event.setActionButton(MotionEvent.BUTTON_PRIMARY);
+ }
assertTrue("injectInputEvent failed",
mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
event.recycle();
@@ -1702,8 +1745,7 @@
public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
GestureScope gestureScope) {
- return movePointer(
- downTime, startTime, duration, false, from, to, gestureScope);
+ return movePointer(downTime, startTime, duration, false, from, to, gestureScope);
}
public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating,
@@ -1763,11 +1805,12 @@
@NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) {
final Point targetCenter = target.getVisibleCenter();
final long downTime = SystemClock.uptimeMillis();
- sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.INSIDE);
+ sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER);
expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
final UiObject2 result = waitForLauncherObject(resName);
sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
- GestureScope.INSIDE);
+ GestureScope.DONT_EXPECT_PILFER);
return result;
}
@@ -1851,6 +1894,12 @@
return tasks;
}
+ /** Reinitializes the workspace to its default layout. */
+ public void reinitializeLauncherData() {
+ getTestInfo(TestProtocol.REQUEST_REINITIALIZE_DATA);
+ }
+
+ /** Clears the workspace, leaving it empty. */
public void clearLauncherData() {
getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
}
@@ -2042,25 +2091,9 @@
final long downTime = SystemClock.uptimeMillis();
final Point tapTarget = new Point(x, y);
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
- LauncherInstrumentation.GestureScope.INSIDE);
- }
- }
-
- /**
- * Sets the consumer to run callbacks at all run-points.
- */
- public void setRunPointCallback(Consumer<CALLBACK_RUN_POINT> callback) {
- mCallbackAtRunPoint = callback;
- }
-
- /**
- * Runs the callback at the specified point if it exists.
- */
- void runCallbackIfActive(CALLBACK_RUN_POINT runPoint) {
- if (mCallbackAtRunPoint != null) {
- mCallbackAtRunPoint.accept(runPoint);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
index 386deac..bd2c9c1 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -19,8 +19,6 @@
import androidx.annotation.NonNull;
import androidx.test.uiautomator.UiObject2;
-import com.android.launcher3.testing.shared.TestProtocol;
-
/**
* View containing overview actions
*/
@@ -51,13 +49,6 @@
"clicked screenshot button")) {
UiObject2 closeScreenshot = mLauncher.waitForSystemUiObject(
"screenshot_dismiss_image");
- if (mLauncher.getNavigationModel()
- != LauncherInstrumentation.NavigationModel.THREE_BUTTON) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
- LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
- mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
- LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
- }
closeScreenshot.click();
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
"dismissed screenshot")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 39b93b4..e4cfc52 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -124,7 +124,7 @@
final int centerY = taskBounds.centerY();
mLauncher.executeAndWaitForLauncherEvent(
() -> mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
- LauncherInstrumentation.GestureScope.INSIDE),
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER),
event -> TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE.equals(event.getClassName()),
() -> "Didn't receive a dismiss animation ends message: " + centerX + ", "
+ centerY, "swiping to dismiss");
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index e349620..7c29a6c 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -50,6 +50,24 @@
}
}
+ /** Taps the app info item from the overview task menu and returns the LaunchedAppState
+ * representing the App info settings page. */
+ @NonNull
+ public LaunchedAppState tapAppInfoMenuItem() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "before tapping the app info menu item")) {
+ mLauncher.clickLauncherObject(
+ mLauncher.findObjectInContainer(mMenu, By.text("App info")));
+
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "tapped app info menu item")) {
+ mLauncher.waitUntilSystemLauncherObjectGone("overview_panel");
+ return new LaunchedAppState(mLauncher);
+ }
+ }
+ }
+
/** Returns true if an item matching the given string is present in the menu. */
public boolean hasMenuItem(String expectedMenuItemText) {
UiObject2 menuItem = mLauncher.findObjectInContainer(mMenu, By.text(expectedMenuItemText));
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java
index b2cc92d..e3035bf 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java
@@ -15,13 +15,7 @@
*/
package com.android.launcher3.tapl;
-import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_ENTER;
-import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOVER_EXIT;
-
-import android.graphics.Point;
import android.graphics.Rect;
-import android.os.SystemClock;
-import android.view.MotionEvent;
import androidx.test.uiautomator.UiObject2;
@@ -42,28 +36,4 @@
public Rect getVisibleBounds() {
return mMenuItem.getVisibleBounds();
}
-
- /**
- * Emulate the cursor entering and exiting a hover over this menu item.
- */
- public void hoverCursor() {
- try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
- LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "cursor hover entering menu item")) {
- long downTime = SystemClock.uptimeMillis();
- mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
- new Point(mMenuItem.getVisibleCenter().x, mMenuItem.getVisibleCenter().y),
- null);
- mLauncher.runCallbackIfActive(CALLBACK_HOVER_ENTER);
-
- try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "cursor hover exiting menu item")) {
- downTime = SystemClock.uptimeMillis();
- mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
- new Point(mMenuItem.getVisibleCenter().x, mMenuItem.getVisibleCenter().y),
- null);
- mLauncher.runCallbackIfActive(CALLBACK_HOVER_EXIT);
- }
- }
- }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java
index 6bc4f21..7f3f61d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Qsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java
@@ -30,13 +30,21 @@
private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
protected final LauncherInstrumentation mLauncher;
+ private final UiObject2 mContainer;
+ private final String mQsbResName;
- protected Qsb(LauncherInstrumentation launcher) {
+ protected Qsb(LauncherInstrumentation launcher, UiObject2 container, String qsbResName) {
mLauncher = launcher;
+ mContainer = container;
+ mQsbResName = qsbResName;
+ waitForQsbObject();
}
// Waits for the quick search box.
- protected abstract UiObject2 waitForQsbObject();
+ private UiObject2 waitForQsbObject() {
+ return mLauncher.waitForObjectInContainer(mContainer, mQsbResName);
+ }
+
/**
* Launch assistant app by tapping mic icon on qsb.
*/
@@ -79,8 +87,12 @@
mLauncher.waitForIdle();
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
"clicked qsb to open search result page")) {
- return new SearchResultFromQsb(mLauncher);
+ return createSearchResult();
}
}
}
+
+ protected SearchResultFromQsb createSearchResult() {
+ return new SearchResultFromQsb(mLauncher);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 80176e9..d02e747 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -32,7 +32,7 @@
// This particular ID change should happen with caution
private static final String SEARCH_CONTAINER_RES_ID = "search_results_list_view";
- private final LauncherInstrumentation mLauncher;
+ protected final LauncherInstrumentation mLauncher;
SearchResultFromQsb(LauncherInstrumentation launcher) {
mLauncher = launcher;
@@ -49,27 +49,33 @@
}
/** Find the app from search results with app name. */
- public Launchable findAppIcon(String appName) {
+ public AppIcon findAppIcon(String appName) {
UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
+ return createAppIcon(icon);
+ }
+
+ protected AppIcon createAppIcon(UiObject2 icon) {
return new AllAppsAppIcon(mLauncher, icon);
}
/** Find the web suggestion from search suggestion's title text */
- public void verifyWebSuggestIsPresent(String text) {
- ArrayList<UiObject2> goldenGateResults =
+ public SearchWebSuggestion findWebSuggestion(String text) {
+ ArrayList<UiObject2> webSuggestions =
new ArrayList<>(mLauncher.waitForObjectsInContainer(
mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
By.clazz(TextView.class)));
- boolean found = false;
- for(UiObject2 uiObject: goldenGateResults) {
+ for (UiObject2 uiObject: webSuggestions) {
String currentString = uiObject.getText();
if (currentString.equals(text)) {
- found = true;
+ return createWebSuggestion(uiObject);
}
}
- if (!found) {
- throw new IllegalStateException("Web suggestion title: " + text + " not found");
- }
+ mLauncher.fail("Web suggestion title: " + text + " not found");
+ return null;
+ }
+
+ protected SearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) {
+ return new SearchWebSuggestion(mLauncher, webSuggestion);
}
/** Find the total amount of views being displayed and return the size */
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
new file mode 100644
index 0000000..6c6ab05
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
@@ -0,0 +1,48 @@
+/*
+ * 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Operations on search result page opened from Taskbar qsb.
+ */
+public class SearchResultFromTaskbarQsb extends SearchResultFromQsb {
+
+ SearchResultFromTaskbarQsb(LauncherInstrumentation launcher) {
+ super(launcher);
+ }
+
+ @Override
+ public TaskbarAppIcon findAppIcon(String appName) {
+ return (TaskbarAppIcon) super.findAppIcon(appName);
+ }
+
+ @Override
+ protected TaskbarAppIcon createAppIcon(UiObject2 icon) {
+ return new TaskbarAppIcon(mLauncher, icon);
+ }
+
+ @Override
+ public TaskbarSearchWebSuggestion findWebSuggestion(String text) {
+ return (TaskbarSearchWebSuggestion) super.findWebSuggestion(text);
+ }
+
+ @Override
+ protected TaskbarSearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) {
+ return new TaskbarSearchWebSuggestion(mLauncher, webSuggestion);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchWebSuggestion.java b/tests/tapl/com/android/launcher3/tapl/SearchWebSuggestion.java
new file mode 100644
index 0000000..e4dec98
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SearchWebSuggestion.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.shared.TestProtocol;
+
+import java.util.regex.Pattern;
+
+/**
+ * Operations on a search web suggestion from a qsb.
+ */
+public class SearchWebSuggestion extends Launchable {
+
+ private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
+
+ SearchWebSuggestion(LauncherInstrumentation launcher, UiObject2 object) {
+ super(launcher, object);
+ }
+
+ @Override
+ protected void expectActivityStartEvents() {
+ }
+
+ @Override
+ protected String launchableType() {
+ return "search web suggestion";
+ }
+
+ @Override
+ protected void waitForLongPressConfirmation() {
+ mLauncher.waitForLauncherObject("popup_container");
+ }
+
+ @Override
+ protected void addExpectedEventsForLongClick() {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, getLongClickEvent());
+ }
+
+ protected Pattern getLongClickEvent() {
+ return LONG_CLICK_EVENT;
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
index 051630e..8671738 100644
--- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -76,13 +76,13 @@
mLauncher.getRealDisplaySize().x - 1, mLauncher.getRealDisplaySize().y - 1);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, stashTarget,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
LauncherInstrumentation.log("hideTaskbar: sent down");
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("pressed down")) {
mLauncher.waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, stashTarget,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
}
} finally {
mLauncher.getTestInfo(REQUEST_DISABLE_MANUAL_TASKBAR_STASHING);
@@ -147,9 +147,9 @@
mLauncher.getRealDisplaySize().y - 1);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAllAppsQsb.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAllAppsQsb.java
new file mode 100644
index 0000000..7cecd3e
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAllAppsQsb.java
@@ -0,0 +1,38 @@
+/*
+ * 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Operations on Taskbar AllApp screen qsb.
+ */
+public class TaskbarAllAppsQsb extends Qsb {
+
+ TaskbarAllAppsQsb(LauncherInstrumentation launcher, UiObject2 allAppsContainer) {
+ super(launcher, allAppsContainer, "search_container_all_apps");
+ }
+
+ @Override
+ public SearchResultFromTaskbarQsb showSearchResult() {
+ return (SearchResultFromTaskbarQsb) super.showSearchResult();
+ }
+
+ @Override
+ protected SearchResultFromTaskbarQsb createSearchResult() {
+ return new SearchResultFromTaskbarQsb(mLauncher);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java
new file mode 100644
index 0000000..cd8ce42
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java
@@ -0,0 +1,45 @@
+/*
+ * 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import java.util.regex.Pattern;
+
+/**
+ * Operations on a search web suggestion from the Taskbar qsb.
+ */
+public class TaskbarSearchWebSuggestion extends SearchWebSuggestion implements
+ SplitscreenDragSource {
+
+ private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onTaskbarItemLongClick");
+
+ TaskbarSearchWebSuggestion(LauncherInstrumentation launcher,
+ UiObject2 object) {
+ super(launcher, object);
+ }
+
+ @Override
+ protected Pattern getLongClickEvent() {
+ return LONG_CLICK_EVENT;
+ }
+
+ /** This method requires public access, however should not be called in tests. */
+ @Override
+ public Launchable getLaunchable() {
+ return this;
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 7bb02cb..6f6f292 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -18,7 +18,6 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
-import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOLD_BEFORE_DROP;
import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -105,7 +104,8 @@
windowCornerRadius,
startY - swipeHeight - mLauncher.getTouchSlop(),
12,
- ALL_APPS_STATE_ORDINAL, LauncherInstrumentation.GestureScope.INSIDE);
+ ALL_APPS_STATE_ORDINAL,
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"swiped to all apps")) {
@@ -387,7 +387,7 @@
Until.hasObject(installerAlert), LauncherInstrumentation.WAIT_TIME_MS));
final UiObject2 ok = device.findObject(By.text("OK"));
assertNotNull("OK button is not shown", ok);
- launcher.clickObject(ok, LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER);
+ launcher.clickObject(ok);
assertTrue("Uninstall alert is not dismissed after clicking OK", device.wait(
Until.gone(installerAlert), LauncherInstrumentation.WAIT_TIME_MS));
@@ -454,7 +454,7 @@
launcher.runToState(
() -> launcher.sendPointer(
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
- LauncherInstrumentation.GestureScope.INSIDE),
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER),
NORMAL_STATE_ORDINAL,
"sending UP event");
if (expectedEvents != null) {
@@ -543,7 +543,7 @@
executeAndWaitForPageScroll(launcher,
() -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
true, downTime, downTime, true,
- LauncherInstrumentation.GestureScope.INSIDE));
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER));
targetDest.x += displayX * (targetDest.x > 0 ? -1 : 1);
dragStart = screenEdge;
}
@@ -552,8 +552,7 @@
// we just have to put move the icon to the destination and drop it
launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
downTime, SystemClock.uptimeMillis(), false,
- LauncherInstrumentation.GestureScope.INSIDE);
- launcher.runCallbackIfActive(CALLBACK_HOLD_BEFORE_DROP);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
}
}
@@ -586,8 +585,7 @@
// we just have to put move the icon to the destination and drop it
launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
downTime, SystemClock.uptimeMillis(), false,
- LauncherInstrumentation.GestureScope.INSIDE);
- launcher.runCallbackIfActive(CALLBACK_HOLD_BEFORE_DROP);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
}
}
@@ -624,7 +622,7 @@
executeAndWaitForPageScroll(launcher,
() -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
true, downTime, downTime, true,
- LauncherInstrumentation.GestureScope.INSIDE));
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER));
currentPage = Workspace.geCurrentPage(launcher);
currentPosition = screenEdge;
}
@@ -653,7 +651,7 @@
launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, true,
downTime, SystemClock.uptimeMillis(), false,
- LauncherInstrumentation.GestureScope.INSIDE);
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
}