Merge changes from topics "b194781951-flags", "b194781951-flags-5", "b194781951-flags-6", "b194781951-flags-7" into sc-v2-dev
* changes:
Remove PluginInitializer.
Remove #handleWtfs from PluginInitializer.
Remove #getPrivilegedPlugins from PluginInitializer.
Rename PluginInstanceManager to PluginActionManager.
diff --git a/Android.bp b/Android.bp
index 45d022f..43d28c9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -224,7 +224,6 @@
srcs: ["proguard.flags"],
}
-
// Library with all the dependencies for building Launcher Go
android_library {
name: "LauncherGoResLib",
@@ -253,3 +252,24 @@
},
}
+// Build rule for Quickstep library
+android_library {
+ name: "Launcher3QuickStepLib",
+ srcs: [
+ ":launcher-src-no-build-config",
+ ],
+ resource_dirs: [
+ "quickstep/res",
+ ],
+ static_libs: [
+ "SystemUI-statsd",
+ "SystemUISharedLib",
+ "Launcher3CommonDepsLib"
+ ],
+ manifest: "quickstep/AndroidManifest.xml",
+ platform_apis: true,
+ min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline-launcher3.xml",
+ },
+}
diff --git a/Android.mk b/Android.mk
index c222f24..c1dbc53 100644
--- a/Android.mk
+++ b/Android.mk
@@ -53,42 +53,6 @@
include $(BUILD_PACKAGE)
#
-# Build rule for Quickstep library.
-#
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT2_ONLY := true
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- SystemUI-statsd \
- SystemUISharedLib
-ifneq (,$(wildcard frameworks/base))
- LOCAL_PRIVATE_PLATFORM_APIS := true
-else
- LOCAL_SDK_VERSION := system_current
- LOCAL_MIN_SDK_VERSION := 26
-endif
-LOCAL_MODULE := Launcher3QuickStepLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, quickstep/src) \
- $(call all-java-files-under, src_shortcuts_overrides)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
-LOCAL_PROGUARD_ENABLED := disabled
-
-
-LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#
# Build rule for Quickstep app.
#
include $(CLEAR_VARS)
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 4eecf29..eee6db5 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -30,7 +30,6 @@
with some minor changed based on the derivative app.
-->
- <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8066816..c72f62d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -31,6 +31,7 @@
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
+ android:debuggable="true"
android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
android:theme="@style/AppTheme"
diff --git a/buglist_with_title.txt b/buglist_with_title.txt
deleted file mode 100644
index aa8b413..0000000
--- a/buglist_with_title.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-144170434 twickham P1 FIXED Improve Overview -> Home transition ----
-149934536 twickham P2 FIXED Update gesture nav pullback logic ----
-154951045 peanutbutter P1 FIXED Odd animation occuring at times when swiping to home ----
-154964045 awickham P2 FIXED "Clear all" text is not in the middle of app's window vertically ----
-158701272 twickham P4 FIXED Discontinuities when long-swiping to home ----
-160361464 tracyzhou P2 FIXED Place launcher above the target app in live tile mode ----
-160568387 twickham P2 FIXED Can't get to app switcher by swiping up (motion pause not detected) ----
-160718310 xuqiu P1 FIXED With "Select" overview action selected, App icon is missing in other overview apps after orientation change ----
-160748731 sunnygoyal P2 ASSIGNED Unify prediction model with Launcher model ----
-160759508 twickham P2 FIXED Swipe up cannot back to home screen in overview. ----
-161273376 xuqiu P2 FIXED [Overview Actions] Add logging and helpful messages ----
-161536946 twickham P2 FIXED Haptics don't indicate snap-to in overview, ----
-161685099 winsonc P2 FIXED Screen still stay at the quick settings/notification when I swipe up with 3 finger to check the all apps. ----
-161801331 hyunyoungs P2 FIXED Change AllAppsSearch plugin to support only data fetch ----
-161901771 xuqiu P1 FIXED Overlapping layer of highlights with app layout getting darker when keep rotating the device from "Feedback" viewpoint in split screen ----
-161939759 sunnygoyal P2 FIXED RD1A: Going to overview in landscape mode clips the screen content ----
-162012217 perumaal P2 ASSIGNED Leaked Activity Caused by Gleams ----
-162454040 bookatz P2 ASSIGNED Create multiuser test that checks that opening an app works properly ----
-162480567 sfufa P4 FIXED Enable Item Decorations for search items ----
-162564471 tracyzhou P2 FIXED [Live tile] Handle tapping overview actions in live tile mode ----
-162623012 zakcohen P1 ASSIGNED Enable chips flag ----
-162812884 winsonc P2 ASSIGNED [R]The color have not changed in some page after turning on the dark theme. ----
-162861289 hyunyoungs P2 FIXED Add FocusIndicator support to DEVICE_SEARCH feature in S ----
-162871508 sfufa P2 ASSIGNED Introduce support for Hero app section ----
diff --git a/go/quickstep/res/values-gu/strings.xml b/go/quickstep/res/values-gu/strings.xml
index 28a2d38..e1a568f 100644
--- a/go/quickstep/res/values-gu/strings.xml
+++ b/go/quickstep/res/values-gu/strings.xml
@@ -4,7 +4,7 @@
<string name="app_share_drop_target_label" msgid="5804774105974539508">"ઍપ શેર કરો"</string>
<string name="action_listen" msgid="2370304050784689486">"સાંભળો"</string>
<string name="action_translate" msgid="8028378961867277746">"અનુવાદ કરો"</string>
- <string name="action_search" msgid="6269564710943755464">"Lens"</string>
+ <string name="action_search" msgid="6269564710943755464">"લેન્સ"</string>
<string name="dialog_acknowledge" msgid="2804025517675853172">"સમજાઈ ગયું"</string>
<string name="dialog_cancel" msgid="6464336969134856366">"રદ કરો"</string>
<string name="dialog_settings" msgid="6564397136021186148">"સેટિંગ"</string>
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index 38c9919..7b3e6c4 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -26,3 +26,19 @@
path: "robolectric_tests",
srcs: ["robolectric_tests/src/**/*.java"],
}
+
+filegroup {
+ name: "launcher3-quickstep-tests-src",
+ path: "tests",
+ srcs: ["tests/src/**/*.java"],
+}
+
+filegroup {
+ name: "launcher3-quickstep-oop-tests-src",
+ path: "tests",
+ srcs: [
+ "tests/src/com/android/quickstep/NavigationModeSwitchRule.java",
+ "tests/src/com/android/quickstep/AbstractQuickStepTest.java",
+ "tests/src/com/android/quickstep/TaplTestsQuickstep.java",
+ ]
+}
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index b43d8d1..dc92731 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -22,11 +22,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3">
- <permission
- android:name="${packageName}.permission.HOTSEAT_EDU"
- android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
- android:protectionLevel="signatureOrSystem" />
-
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
@@ -41,7 +36,6 @@
<uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY"/>
<uses-permission android:name="android.permission.MONITOR_INPUT"/>
- <uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
<uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
<application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -133,20 +127,6 @@
</intent-filter>
</activity>
- <activity
- android:name=".hybridhotseat.HotseatEduActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:noHistory="true"
- android:launchMode="singleTask"
- android:clearTaskOnLaunch="true"
- android:permission="${packageName}.permission.HOTSEAT_EDU"
- android:exported="true">
- <intent-filter>
- <action android:name="com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"/>
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
</application>
</manifest>
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
new file mode 100644
index 0000000..47cbd9f
--- /dev/null
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ripple android:color="?android:attr/colorControlHighlight"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item>
+ <shape android:shape="rectangle"
+ android:tint="?colorButtonNormal">
+ <corners android:radius="18dp" />
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/quickstep/res/drawable/button_taskbar_edu_bordered.xml b/quickstep/res/drawable/button_taskbar_edu_bordered.xml
new file mode 100644
index 0000000..47f8e8f
--- /dev/null
+++ b/quickstep/res/drawable/button_taskbar_edu_bordered.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp" />
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/quickstep/res/drawable/button_taskbar_edu_colored.xml b/quickstep/res/drawable/button_taskbar_edu_colored.xml
new file mode 100644
index 0000000..70bfc9f
--- /dev/null
+++ b/quickstep/res/drawable/button_taskbar_edu_colored.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_finger_dot.xml b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml
new file mode 100644
index 0000000..5f8aafd
--- /dev/null
+++ b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="@color/gesture_tutorial_primary_color" />
+ <size android:width="92dp" android:height="92dp"/>
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_back.xml b/quickstep/res/drawable/gesture_tutorial_motion_back.xml
deleted file mode 100644
index a6860fa..0000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_back.xml
+++ /dev/null
@@ -1,1233 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_1_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_3_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_4_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_5_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_6_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_7_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_8_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_9_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_10_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_11_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_12_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G_D_13_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillAlpha"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="1367"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="283"
- android:propertyName="scaleX"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0.88012"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,0.536 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="283"
- android:propertyName="scaleY"
- android:startOffset="1217"
- android:valueFrom="1"
- android:valueTo="0.88012"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,0.536 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2417"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_1_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_2_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_3_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_4_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_5_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_6_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_7_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_8_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_9_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_10_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_11_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_12_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_13_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_14_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_15_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_16_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_17_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_18_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_19_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_20_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_21_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_22_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_23_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G_D_24_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="333"
- android:propertyName="fillAlpha"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="1417"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="667"
- android:pathData="M 123.282,129.757C 134.28199999999998,129.757 178.282,129.757 189.282,129.757"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="217">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="333"
- android:pathData="M 189.282,129.757C 189.282,129.757 189.282,129.757 189.282,129.757"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="883">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="250"
- android:pathData="M 189.282,129.757C 178.282,129.757 134.28199999999998,129.757 123.282,129.757"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="1217">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="217"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2383"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="fillAlpha"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="967"
- android:propertyName="fillAlpha"
- android:startOffset="217"
- android:valueFrom="0.75"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="1183"
- android:valueFrom="0.75"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="pathData"
- android:startOffset="0"
- android:valueFrom="M-206.5 13.5 C-186.34,13.5 -170,29.84 -170,50 C-170,70.16 -186.34,86.5 -206.5,86.5 C-226.66,86.5 -243,70.16 -243,50 C-243,29.84 -226.66,13.5 -206.5,13.5c "
- android:valueTo="M-206 0 C-178.39,0 -156,22.39 -156,50 C-156,77.61 -178.39,100 -206,100 C-233.61,100 -256,77.61 -256,50 C-256,22.39 -233.61,0 -206,0c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="583"
- android:propertyName="pathData"
- android:startOffset="217"
- android:valueFrom="M-206 0 C-178.39,0 -156,22.39 -156,50 C-156,77.61 -178.39,100 -206,100 C-233.61,100 -256,77.61 -256,50 C-256,22.39 -233.61,0 -206,0c "
- android:valueTo="M0 0 C27.61,0 50,22.39 50,50 C50,77.61 27.61,100 0,100 C-27.61,100 -50,77.61 -50,50 C-50,22.39 -27.61,0 0,0c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="383"
- android:propertyName="pathData"
- android:startOffset="800"
- android:valueFrom="M0 0 C27.61,0 50,22.39 50,50 C50,77.61 27.61,100 0,100 C-27.61,100 -50,77.61 -50,50 C-50,22.39 -27.61,0 0,0c "
- android:valueTo="M0 0 C27.61,0 50,22.39 50,50 C50,77.61 27.61,100 0,100 C-27.61,100 -50,77.61 -50,50 C-50,22.39 -27.61,0 0,0c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="pathData"
- android:startOffset="1183"
- android:valueFrom="M0 0 C27.61,0 50,22.39 50,50 C50,77.61 27.61,100 0,100 C-27.61,100 -50,77.61 -50,50 C-50,22.39 -27.61,0 0,0c "
- android:valueTo="M0 13.5 C20.16,13.5 36.5,29.84 36.5,50 C36.5,70.16 20.16,86.5 0,86.5 C-20.16,86.5 -36.5,70.16 -36.5,50 C-36.5,29.84 -20.16,13.5 0,13.5c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="1767"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group
- android:name="_R_G_L_4_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -446 C206,-446 206,446 206,446 C206,446 -206,446 -206,446 C-206,446 -206,-446 -206,-446 C-206,-446 206,-446 206,-446c " />
- </group>
- <group
- android:name="_R_G_L_3_G"
- android:pivotX="206"
- android:pivotY="446"
- android:scaleX="1"
- android:scaleY="1">
- <group android:name="_R_G_L_3_G_L_0_G">
- <group android:name="_R_G_L_3_G_L_0_G_L_0_G">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#f1f3f4"
- android:fillType="nonZero"
- android:pathData=" M412 101 C412,101 412,892 412,892 C412,892 0,892 0,892 C0,892 0,101 0,101 C0,101 412,101 412,101c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_1_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M412 0 C412,0 412,101 412,101 C412,101 0,101 0,101 C0,101 0,0 0,0 C0,0 412,0 412,0c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_2_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M383 804 C383,816.15 373.15,826 361,826 C361,826 51,826 51,826 C38.85,826 29,816.15 29,804 C29,791.85 38.85,782 51,782 C51,782 361,782 361,782 C373.15,782 383,791.85 383,804c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_3_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M201 47 C201,47 201,75 201,75 C201,77.21 199.21,79 197,79 C197,79 38,79 38,79 C35.79,79 34,77.21 34,75 C34,75 34,47 34,47 C34,44.79 35.79,43 38,43 C38,43 197,43 197,43 C199.21,43 201,44.79 201,47c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_4_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M388 47 C388,47 388,75 388,75 C388,77.21 386.21,79 384,79 C384,79 356,79 356,79 C353.79,79 352,77.21 352,75 C352,75 352,47 352,47 C352,44.79 353.79,43 356,43 C356,43 384,43 384,43 C386.21,43 388,44.79 388,47c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_5_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M336 47 C336,47 336,75 336,75 C336,77.21 334.21,79 332,79 C332,79 304,79 304,79 C301.79,79 300,77.21 300,75 C300,75 300,47 300,47 C300,44.79 301.79,43 304,43 C304,43 332,43 332,43 C334.21,43 336,44.79 336,47c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_6_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M70 618 C70,630.15 60.15,640 48,640 C35.85,640 26,630.15 26,618 C26,605.85 35.85,596 48,596 C60.15,596 70,605.85 70,618c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_7_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M70 396 C70,408.15 60.15,418 48,418 C35.85,418 26,408.15 26,396 C26,383.85 35.85,374 48,374 C60.15,374 70,383.85 70,396c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_8_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M394 248 C394,248 394,324 394,324 C394,333.94 385.94,342 376,342 C376,342 142,342 142,342 C132.06,342 124,333.94 124,324 C124,324 124,248 124,248 C124,238.06 132.06,230 142,230 C142,230 376,230 376,230 C385.94,230 394,238.06 394,248c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_9_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M393.94 468.38 C393.94,468.38 393.94,481.5 393.94,481.5 C393.94,483.71 392.15,485.5 389.94,485.5 C389.94,485.5 303.5,485.5 303.5,485.5 C301.29,485.5 299.5,483.71 299.5,481.5 C299.5,481.5 299.5,468.38 299.5,468.38 C299.5,466.17 301.29,464.38 303.5,464.38 C303.5,464.38 389.94,464.38 389.94,464.38 C392.15,464.38 393.94,466.17 393.94,468.38c M394 468 C394,477.67 386.17,485.5 376.5,485.5 C376.5,485.5 290,485.5 290,485.5 C280.33,485.5 272.5,477.67 272.5,468 C272.5,458.34 280.33,450.5 290,450.5 C290,450.5 376.5,450.5 376.5,450.5 C386.17,450.5 394,458.34 394,468c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_10_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M394 494 C394,494 394,547 394,547 C394,549.21 392.21,551 390,551 C390,551 164,551 164,551 C161.79,551 160,549.21 160,547 C160,547 160,494 160,494 C160,491.79 161.79,490 164,490 C164,490 390,490 390,490 C392.21,490 394,491.79 394,494c M394 508 C394,508 394,545 394,545 C394,554.94 385.94,563 376,563 C376,563 142,563 142,563 C132.06,563 124,554.94 124,545 C124,545 124,508 124,508 C124,498.06 132.06,490 142,490 C142,490 376,490 376,490 C385.94,490 394,498.06 394,508c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_11_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M394 690 C394,690 394,727 394,727 C394,736.94 385.94,745 376,745 C376,745 142,745 142,745 C132.06,745 124,736.94 124,727 C124,727 124,690 124,690 C124,680.06 132.06,672 142,672 C142,672 376,672 376,672 C385.94,672 394,680.06 394,690c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_12_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M267.5 617 C267.5,626.67 259.67,634.5 250,634.5 C250,634.5 104.5,634.5 104.5,634.5 C94.84,634.5 87,626.67 87,617 C87,607.34 94.84,599.5 104.5,599.5 C104.5,599.5 250,599.5 250,599.5 C259.67,599.5 267.5,607.34 267.5,617c " />
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_13_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M299 395.5 C299,405.17 291.16,413 281.5,413 C281.5,413 104.5,413 104.5,413 C94.84,413 87,405.17 87,395.5 C87,385.84 94.84,378 104.5,378 C104.5,378 281.5,378 281.5,378 C291.16,378 299,385.84 299,395.5c " />
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_2_G"
- android:scaleY="0">
- <group
- android:name="_R_G_L_2_G_L_0_G"
- android:scaleY="0">
- <group
- android:name="_R_G_L_2_G_L_0_G_L_0_G"
- android:scaleY="0">
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M412 0 C412,0 412,892 412,892 C412,892 0,892 0,892 C0,892 0,0 0,0 C0,0 412,0 412,0c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_1_P_0"
- android:fillAlpha="0"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M412 0 C412,0 412,101 412,101 C412,101 0,101 0,101 C0,101 0,0 0,0 C0,0 412,0 412,0c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_2_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M195 143 C195,143 195,153 195,153 C195,155.21 193.21,157 191,157 C191,157 106,157 106,157 C103.79,157 102,155.21 102,153 C102,153 102,143 102,143 C102,140.79 103.79,139 106,139 C106,139 191,139 191,139 C193.21,139 195,140.79 195,143c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_3_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M270 165 C270,165 270,173 270,173 C270,175.21 268.21,177 266,177 C266,177 106,177 106,177 C103.79,177 102,175.21 102,173 C102,173 102,165 102,165 C102,162.79 103.79,161 106,161 C106,161 266,161 266,161 C268.21,161 270,162.79 270,165c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_4_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M222 231 C222,231 222,241 222,241 C222,243.21 220.21,245 218,245 C218,245 106,245 106,245 C103.79,245 102,243.21 102,241 C102,241 102,231 102,231 C102,228.79 103.79,227 106,227 C106,227 218,227 218,227 C220.21,227 222,228.79 222,231c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_5_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M241 253 C241,253 241,261 241,261 C241,263.21 239.21,265 237,265 C237,265 106,265 106,265 C103.79,265 102,263.21 102,261 C102,261 102,253 102,253 C102,250.79 103.79,249 106,249 C106,249 237,249 237,249 C239.21,249 241,250.79 241,253c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_6_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M214 319 C214,319 214,329 214,329 C214,331.21 212.21,333 210,333 C210,333 106,333 106,333 C103.79,333 102,331.21 102,329 C102,329 102,319 102,319 C102,316.79 103.79,315 106,315 C106,315 210,315 210,315 C212.21,315 214,316.79 214,319c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_7_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M333 341 C333,341 333,349 333,349 C333,351.21 331.21,353 329,353 C329,353 106,353 106,353 C103.79,353 102,351.21 102,349 C102,349 102,341 102,341 C102,338.79 103.79,337 106,337 C106,337 329,337 329,337 C331.21,337 333,338.79 333,341c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_8_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M238 407 C238,407 238,417 238,417 C238,419.21 236.21,421 234,421 C234,421 106,421 106,421 C103.79,421 102,419.21 102,417 C102,417 102,407 102,407 C102,404.79 103.79,403 106,403 C106,403 234,403 234,403 C236.21,403 238,404.79 238,407c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_9_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M295 429 C295,429 295,437 295,437 C295,439.21 293.21,441 291,441 C291,441 106,441 106,441 C103.79,441 102,439.21 102,437 C102,437 102,429 102,429 C102,426.79 103.79,425 106,425 C106,425 291,425 291,425 C293.21,425 295,426.79 295,429c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_10_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M168 495 C168,495 168,505 168,505 C168,507.21 166.21,509 164,509 C164,509 106,509 106,509 C103.79,509 102,507.21 102,505 C102,505 102,495 102,495 C102,492.79 103.79,491 106,491 C106,491 164,491 164,491 C166.21,491 168,492.79 168,495c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_11_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M269 517 C269,517 269,525 269,525 C269,527.21 267.21,529 265,529 C265,529 106,529 106,529 C103.79,529 102,527.21 102,525 C102,525 102,517 102,517 C102,514.79 103.79,513 106,513 C106,513 265,513 265,513 C267.21,513 269,514.79 269,517c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_12_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M235 583 C235,583 235,593 235,593 C235,595.21 233.21,597 231,597 C231,597 106,597 106,597 C103.79,597 102,595.21 102,593 C102,593 102,583 102,583 C102,580.79 103.79,579 106,579 C106,579 231,579 231,579 C233.21,579 235,580.79 235,583c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_13_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M295 605 C295,605 295,613 295,613 C295,615.21 293.21,617 291,617 C291,617 106,617 106,617 C103.79,617 102,615.21 102,613 C102,613 102,605 102,605 C102,602.79 103.79,601 106,601 C106,601 291,601 291,601 C293.21,601 295,602.79 295,605c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_14_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M223 671 C223,671 223,681 223,681 C223,683.21 221.21,685 219,685 C219,685 106,685 106,685 C103.79,685 102,683.21 102,681 C102,681 102,671 102,671 C102,668.79 103.79,667 106,667 C106,667 219,667 219,667 C221.21,667 223,668.79 223,671c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_15_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M246 693 C246,693 246,701 246,701 C246,703.21 244.21,705 242,705 C242,705 106,705 106,705 C103.79,705 102,703.21 102,701 C102,701 102,693 102,693 C102,690.79 103.79,689 106,689 C106,689 242,689 242,689 C244.21,689 246,690.79 246,693c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_16_P_0"
- android:fillAlpha="0"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M388 798 C388,798 388,798 388,798 C388,813.45 375.45,826 360,826 C360,826 267,826 267,826 C251.55,826 239,813.45 239,798 C239,798 239,798 239,798 C239,782.55 251.55,770 267,770 C267,770 360,770 360,770 C375.45,770 388,782.55 388,798c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_17_P_0"
- android:fillAlpha="0"
- android:fillColor="#f8f9fa"
- android:fillType="nonZero"
- android:pathData=" M377 47 C377,47 377,75 377,75 C377,77.21 375.21,79 373,79 C373,79 38,79 38,79 C35.79,79 34,77.21 34,75 C34,75 34,47 34,47 C34,44.79 35.79,43 38,43 C38,43 373,43 373,43 C375.21,43 377,44.79 377,47c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_18_P_0"
- android:fillAlpha="0"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M82 157 C82,172.46 69.46,185 54,185 C38.54,185 26,172.46 26,157 C26,141.54 38.54,129 54,129 C69.46,129 82,141.54 82,157c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_19_P_0"
- android:fillAlpha="0"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M82 245 C82,260.46 69.46,273 54,273 C38.54,273 26,260.46 26,245 C26,229.54 38.54,217 54,217 C69.46,217 82,229.54 82,245c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_20_P_0"
- android:fillAlpha="0"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M82 333 C82,348.46 69.46,361 54,361 C38.54,361 26,348.46 26,333 C26,317.54 38.54,305 54,305 C69.46,305 82,317.54 82,333c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_21_P_0"
- android:fillAlpha="0"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M82 421 C82,436.46 69.46,449 54,449 C38.54,449 26,436.46 26,421 C26,405.54 38.54,393 54,393 C69.46,393 82,405.54 82,421c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_22_P_0"
- android:fillAlpha="0"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M82 509 C82,524.46 69.46,537 54,537 C38.54,537 26,524.46 26,509 C26,493.54 38.54,481 54,481 C69.46,481 82,493.54 82,509c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_23_P_0"
- android:fillAlpha="0"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M82 597 C82,612.46 69.46,625 54,625 C38.54,625 26,612.46 26,597 C26,581.54 38.54,569 54,569 C69.46,569 82,581.54 82,597c " />
- <path
- android:name="_R_G_L_2_G_L_0_G_L_0_G_D_24_P_0"
- android:fillAlpha="0"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M82 685 C82,700.46 69.46,713 54,713 C38.54,713 26,700.46 26,685 C26,669.54 38.54,657 54,657 C69.46,657 82,669.54 82,685c " />
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G"
- android:scaleY="0"
- android:translateX="-17.875"
- android:translateY="322.017">
- <group
- android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"
- android:translateX="123.282"
- android:translateY="129.757">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#3b4043"
- android:fillType="nonZero"
- android:pathData=" M-109 27.43 C-109,27.43 -112.61,23.81 -112.61,23.81 C-112.61,23.81 -133.03,44.23 -133.03,44.23 C-133.03,44.23 -112.61,64.64 -112.61,64.64 C-112.61,64.64 -109,61.03 -109,61.03 C-109,61.03 -125.8,44.23 -125.8,44.23 C-125.8,44.23 -109,27.43 -109,27.43c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="@color/gesture_tutorial_primary_color"
- android:fillType="nonZero"
- android:pathData=" M-206.5 13.5 C-186.34,13.5 -170,29.84 -170,50 C-170,70.16 -186.34,86.5 -206.5,86.5 C-226.66,86.5 -243,70.16 -243,50 C-243,29.84 -226.66,13.5 -206.5,13.5c " />
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml
deleted file mode 100644
index aff35c1..0000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml
+++ /dev/null
@@ -1,1254 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="_R_G_L_2_G_L_4_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="50"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="650"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="50"
- android:pathData="M 206,776C 206,776 206,776 206,776"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="700"
- android:pathData="M 206,776C 206,776 206,797 206,797"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="650">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="650"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_3_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 56,673C 56,673 56,706 56,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 156,673C 156,673 156,706 156,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 256,673C 256,673 256,706 256,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 356,673C 356,673 356,706 356,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_11_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#dadce0"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.215 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_10_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_9_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_8_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_7_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_6_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_5_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_4_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_3_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_3_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#e8eaed"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.232 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#80868b"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.674 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#80868b"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.674 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="150"
- android:propertyName="fillColor"
- android:startOffset="350"
- android:valueFrom="#202124"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.69 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="150"
- android:propertyName="fillColor"
- android:startOffset="350"
- android:valueFrom="#202124"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.69 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="150"
- android:propertyName="fillColor"
- android:startOffset="350"
- android:valueFrom="#3c4043"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.69 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillColor"
- android:startOffset="500"
- android:valueFrom="#bac4d6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="50"
- android:propertyName="fillColor"
- android:startOffset="633"
- android:valueFrom="#bac4d6"
- android:valueTo="#8ab4f8"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,-0.214 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="283"
- android:propertyName="fillAlpha"
- android:startOffset="500"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.999,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="283"
- android:propertyName="pathData"
- android:startOffset="500"
- android:valueFrom="M206.06 -430.06 C206.06,-430.06 206,431 206,431 C206,446 189.75,446 189.79,446 C189.79,446 -189.98,446 -189.98,446 C-189.94,446 -206,446 -206,431 C-206,431 -206,-430 -206,-430 C-206,-446 -189.97,-446 -190.01,-446 C-190.01,-446 188.98,-446.06 188.98,-446.06 C188.94,-446.06 206,-446 206.06,-430.06c "
- android:valueTo="M60 -0.06 C60,-0.06 60,0.06 60,0.06 C60,28 36,60.25 -0.02,60.25 C-0.02,60.25 0.02,60.25 0.02,60.25 C-32.5,60.25 -60,31.5 -60,0.06 C-60,0.06 -60,-0.06 -60,-0.06 C-60,-31.25 -34,-59.25 0.02,-59.25 C0.02,-59.25 -0.02,-59.25 -0.02,-59.25 C33.5,-59.25 60,-38 60,-0.06c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.999,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="500"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="850"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_T_1">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="283"
- android:pathData="M 206,446C 201.417,411.133 195,385.297 195,385.297"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="217">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="333"
- android:pathData="M 195,385.297C 195,385.297 105.5,148.09000000000003 56,691.5"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="500">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.443,0.093 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_T_1">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="467"
- android:propertyName="scaleX"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="467"
- android:propertyName="scaleY"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_T_1">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2167"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="fillAlpha"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="233"
- android:propertyName="fillAlpha"
- android:startOffset="217"
- android:valueFrom="0.75"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="450"
- android:valueFrom="0.75"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="pathData"
- android:startOffset="0"
- android:valueFrom="M0 411 C19.33,411 35,426.67 35,446 C35,465.33 19.33,481 0,481 C-19.33,481 -35,465.33 -35,446 C-35,426.67 -19.33,411 0,411c "
- android:valueTo="M0 396 C27.61,396 50,418.39 50,446 C50,473.61 27.61,496 0,496 C-27.61,496 -50,473.61 -50,446 C-50,418.39 -27.61,396 0,396c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="433"
- android:propertyName="pathData"
- android:startOffset="217"
- android:valueFrom="M0 396 C27.61,396 50,418.39 50,446 C50,473.61 27.61,496 0,496 C-27.61,496 -50,473.61 -50,446 C-50,418.39 -27.61,396 0,396c "
- android:valueTo="M0 68 C27.61,68 50,90.39 50,118 C50,145.61 27.61,168 0,168 C-27.61,168 -50,145.61 -50,118 C-50,90.39 -27.61,68 0,68c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2167"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="1367"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group
- android:name="_R_G_L_3_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/fake_wallpaper_color_dark_mode"
- android:fillType="nonZero"
- android:pathData=" M206 -446 C206,-446 206,446 206,446 C206,446 -206,446 -206,446 C-206,446 -206,-446 -206,-446 C-206,-446 206,-446 206,-446c " />
- </group>
- <group
- android:name="_R_G_L_2_G"
- android:scaleY="0">
- <group
- android:name="_R_G_L_2_G_L_4_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="776">
- <path
- android:name="_R_G_L_2_G_L_4_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#3c4043"
- android:fillType="nonZero"
- android:pathData=" M180 0 C180,0 180,0 180,0 C180,13.8 168.8,25 155,25 C155,25 -155,25 -155,25 C-168.8,25 -180,13.8 -180,0 C-180,0 -180,0 -180,0 C-180,-13.8 -168.8,-25 -155,-25 C-155,-25 155,-25 155,-25 C168.8,-25 180,-13.8 180,0c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_3_G"
- android:scaleY="0"
- android:translateX="56"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_3_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#8ab4f8"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_2_G"
- android:scaleY="0"
- android:translateX="156"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_2_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#f28b82"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_1_G"
- android:scaleY="0"
- android:translateX="256"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_1_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#fdd663"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_0_G"
- android:scaleY="0"
- android:translateX="356"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#81c995"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_T_1"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="206"
- android:translateY="446">
- <group
- android:name="_R_G_L_1_G"
- android:translateX="-206"
- android:translateY="-446">
- <group android:name="_R_G_L_1_G_L_4_G">
- <group
- android:name="_R_G_L_1_G_L_4_G_L_11_G"
- android:scaleX="0.87473"
- android:scaleY="0.98643"
- android:translateX="206"
- android:translateY="472.769">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M235.5 -407 C235.5,-407 235.5,407 235.5,407 C235.5,416.93 227.43,425 217.5,425 C217.5,425 -217.5,425 -217.5,425 C-227.43,425 -235.5,416.93 -235.5,407 C-235.5,407 -235.5,-407 -235.5,-407 C-235.5,-416.93 -227.43,-425 -217.5,-425 C-217.5,-425 217.5,-425 217.5,-425 C227.43,-425 235.5,-416.93 235.5,-407c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_10_G"
- android:translateX="182.5"
- android:translateY="831">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_9_G"
- android:translateX="186"
- android:translateY="801">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -3 C162,-3 162,3 162,3 C162,7.42 158.42,11 154,11 C154,11 -154,11 -154,11 C-158.42,11 -162,7.42 -162,3 C-162,3 -162,-3 -162,-3 C-162,-7.42 -158.42,-11 -154,-11 C-154,-11 154,-11 154,-11 C158.42,-11 162,-7.42 162,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_8_G"
- android:translateX="119"
- android:translateY="755">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M95 -3 C95,-3 95,3 95,3 C95,7.42 91.42,11 87,11 C87,11 -87,11 -87,11 C-91.42,11 -95,7.42 -95,3 C-95,3 -95,-3 -95,-3 C-95,-7.42 -91.42,-11 -87,-11 C-87,-11 87,-11 87,-11 C91.42,-11 95,-7.42 95,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_7_G"
- android:translateX="182.5"
- android:translateY="725">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_6_G"
- android:translateX="197.5"
- android:translateY="695">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M173.5 -3 C173.5,-3 173.5,3 173.5,3 C173.5,7.42 169.92,11 165.5,11 C165.5,11 -165.5,11 -165.5,11 C-169.92,11 -173.5,7.42 -173.5,3 C-173.5,3 -173.5,-3 -173.5,-3 C-173.5,-7.42 -169.92,-11 -165.5,-11 C-165.5,-11 165.5,-11 165.5,-11 C169.92,-11 173.5,-7.42 173.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_5_G"
- android:translateX="192"
- android:translateY="665">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M168 -3 C168,-3 168,3 168,3 C168,7.42 164.42,11 160,11 C160,11 -160,11 -160,11 C-164.42,11 -168,7.42 -168,3 C-168,3 -168,-3 -168,-3 C-168,-7.42 -164.42,-11 -160,-11 C-160,-11 160,-11 160,-11 C164.42,-11 168,-7.42 168,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_4_G"
- android:translateX="105.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_3_G"
- android:translateX="47.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_2_G"
- android:translateX="142.5"
- android:translateY="328">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M118.5 -10 C118.5,-10 118.5,10 118.5,10 C118.5,14.42 114.92,18 110.5,18 C110.5,18 -110.5,18 -110.5,18 C-114.92,18 -118.5,14.42 -118.5,10 C-118.5,10 -118.5,-10 -118.5,-10 C-118.5,-14.42 -114.92,-18 -110.5,-18 C-110.5,-18 110.5,-18 110.5,-18 C114.92,-18 118.5,-14.42 118.5,-10c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_1_G"
- android:translateX="186"
- android:translateY="284">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -10 C162,-10 162,10 162,10 C162,14.42 158.42,18 154,18 C154,18 -154,18 -154,18 C-158.42,18 -162,14.42 -162,10 C-162,10 -162,-10 -162,-10 C-162,-14.42 -158.42,-18 -154,-18 C-154,-18 154,-18 154,-18 C158.42,-18 162,-14.42 162,-10c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_0_G"
- android:translateX="155"
- android:translateY="240">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M131 -10 C131,-10 131,10 131,10 C131,14.42 127.42,18 123,18 C123,18 -123,18 -123,18 C-127.42,18 -131,14.42 -131,10 C-131,10 -131,-10 -131,-10 C-131,-14.42 -127.42,-18 -123,-18 C-123,-18 123,-18 123,-18 C127.42,-18 131,-14.42 131,-10c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_L_3_G"
- android:translateX="24"
- android:translateY="390">
- <group
- android:name="_R_G_L_1_G_L_3_G_L_0_G"
- android:translateX="182"
- android:translateY="120">
- <path
- android:name="_R_G_L_1_G_L_3_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M182 -98 C182,-98 182,98 182,98 C182,110.14 172.14,120 160,120 C160,120 -160,120 -160,120 C-172.14,120 -182,110.14 -182,98 C-182,98 -182,-98 -182,-98 C-182,-110.14 -172.14,-120 -160,-120 C-160,-120 160,-120 160,-120 C172.14,-120 182,-110.14 182,-98c " />
- </group>
- </group>
- <group android:name="_R_G_L_1_G_L_2_G">
- <group
- android:name="_R_G_L_1_G_L_2_G_L_2_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_1_G_L_2_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -95.63 C206,-95.63 206,42.37 206,42.37 C206,43.47 205.1,44.37 204,44.37 C204,44.37 -204,44.37 -204,44.37 C-205.1,44.37 -206,43.47 -206,42.37 C-206,42.37 -206,-95.63 -206,-95.63 C-206,-96.73 -205.1,-97.63 -204,-97.63 C-204,-97.63 204,-97.63 204,-97.63 C205.1,-97.63 206,-96.73 206,-95.63c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_2_G_L_1_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_1_G_L_2_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M109 -14 C109,-14 109,14 109,14 C109,15.1 108.1,16 107,16 C107,16 -107,16 -107,16 C-108.1,16 -109,15.1 -109,14 C-109,14 -109,-14 -109,-14 C-109,-15.1 -108.1,-16 -107,-16 C-107,-16 107,-16 107,-16 C108.1,-16 109,-15.1 109,-14c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_2_G_L_0_G"
- android:translateX="46"
- android:translateY="145">
- <path
- android:name="_R_G_L_1_G_L_2_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M22 -14 C22,-14 22,14 22,14 C22,18.42 18.42,22 14,22 C14,22 -14,22 -14,22 C-18.42,22 -22,18.42 -22,14 C-22,14 -22,-14 -22,-14 C-22,-18.42 -18.42,-22 -14,-22 C-14,-22 14,-22 14,-22 C18.42,-22 22,-18.42 22,-14c " />
- </group>
- </group>
- <group android:name="_R_G_L_1_G_L_1_G">
- <group
- android:name="_R_G_L_1_G_L_1_G_L_2_G"
- android:translateX="206"
- android:translateY="51">
- <path
- android:name="_R_G_L_1_G_L_1_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#202124"
- android:fillType="nonZero"
- android:pathData=" M206 -0.27 C206,-0.27 206,49.73 206,49.73 C206,49.73 -206,49.73 -206,49.73 C-206,49.73 -206,-0.27 -206,-0.27 C-206,-0.27 206,-0.27 206,-0.27c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_1_G_L_1_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_1_G_L_1_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#202124"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_1_G_L_0_G"
- android:translateX="206"
- android:translateY="66.5">
- <path
- android:name="_R_G_L_1_G_L_1_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#3c4043"
- android:fillType="nonZero"
- android:pathData=" M190 0 C190,0 190,0 190,0 C190,10.21 181.71,18.5 171.5,18.5 C171.5,18.5 -171.5,18.5 -171.5,18.5 C-181.71,18.5 -190,10.21 -190,0 C-190,0 -190,0 -190,0 C-190,-10.21 -181.71,-18.5 -171.5,-18.5 C-171.5,-18.5 171.5,-18.5 171.5,-18.5 C181.71,-18.5 190,-10.21 190,0c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_L_0_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_1_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bac4d6"
- android:fillType="nonZero"
- android:pathData=" M206.06 -430.06 C206.06,-430.06 206,431 206,431 C206,446 189.75,446 189.79,446 C189.79,446 -189.98,446 -189.98,446 C-189.94,446 -206,446 -206,431 C-206,431 -206,-430 -206,-430 C-206,-446 -189.97,-446 -190.01,-446 C-190.01,-446 188.98,-446.06 188.98,-446.06 C188.94,-446.06 206,-446 206.06,-430.06c " />
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="@color/gesture_tutorial_primary_color"
- android:fillType="nonZero"
- android:pathData=" M0 411 C19.33,411 35,426.67 35,446 C35,465.33 19.33,481 0,481 C-19.33,481 -35,465.33 -35,446 C-35,426.67 -19.33,411 0,411c " />
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml
deleted file mode 100644
index 98d97ad..0000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml
+++ /dev/null
@@ -1,1254 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="_R_G_L_2_G_L_4_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="50"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="650"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="50"
- android:pathData="M 206,776C 206,776 206,776 206,776"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="700"
- android:pathData="M 206,776C 206,776 206,797 206,797"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="650">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="650"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_3_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 56,673C 56,673 56,706 56,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 156,673C 156,673 156,706 156,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 256,673C 256,673 256,706 256,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="700"
- android:pathData="M 356,673C 356,673 356,706 356,706"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.27,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="600"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_11_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#dadce0"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.215 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_10_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_9_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_8_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_7_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_6_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_5_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_4_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_3_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_3_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#bdc1c6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.92 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#e8eaed"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,1.232 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#80868b"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.674 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="100"
- android:propertyName="fillColor"
- android:startOffset="400"
- android:valueFrom="#80868b"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.674 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G_L_2_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="150"
- android:propertyName="fillColor"
- android:startOffset="350"
- android:valueFrom="#6e7175"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.674 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G_L_1_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="150"
- android:propertyName="fillColor"
- android:startOffset="350"
- android:valueFrom="#6e7175"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.676 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="150"
- android:propertyName="fillColor"
- android:startOffset="350"
- android:valueFrom="#9a9a9a"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.584 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="517"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="133"
- android:propertyName="fillColor"
- android:startOffset="500"
- android:valueFrom="#bac4d6"
- android:valueTo="#bac4d6"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="50"
- android:propertyName="fillColor"
- android:startOffset="633"
- android:valueFrom="#bac4d6"
- android:valueTo="#8ab4f8"
- android:valueType="colorType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,-0.214 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="283"
- android:propertyName="fillAlpha"
- android:startOffset="500"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.999,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="283"
- android:propertyName="pathData"
- android:startOffset="500"
- android:valueFrom="M206.06 -430.06 C206.06,-430.06 206,431 206,431 C206,446 189.75,446 189.79,446 C189.79,446 -189.98,446 -189.98,446 C-189.94,446 -206,446 -206,431 C-206,431 -206,-430 -206,-430 C-206,-446 -189.97,-446 -190.01,-446 C-190.01,-446 188.98,-446.06 188.98,-446.06 C188.94,-446.06 206,-446 206.06,-430.06c "
- android:valueTo="M60 -0.06 C60,-0.06 60,0.06 60,0.06 C60,28 36,60.25 -0.02,60.25 C-0.02,60.25 0.02,60.25 0.02,60.25 C-32.5,60.25 -60,31.5 -60,0.06 C-60,0.06 -60,-0.06 -60,-0.06 C-60,-31.25 -34,-59.25 0.02,-59.25 C0.02,-59.25 -0.02,-59.25 -0.02,-59.25 C33.5,-59.25 60,-38 60,-0.06c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.999,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="500"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="850"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_T_1">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="283"
- android:pathData="M 206,446C 201.417,411.133 195,385.297 195,385.297"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="217">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="333"
- android:pathData="M 195,385.297C 195,385.297 105.5,148.09000000000003 56,691.5"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="500">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.443,0.093 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_T_1">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="467"
- android:propertyName="scaleX"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="467"
- android:propertyName="scaleY"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_T_1">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2167"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="fillAlpha"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="233"
- android:propertyName="fillAlpha"
- android:startOffset="217"
- android:valueFrom="0.75"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="450"
- android:valueFrom="0.75"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="pathData"
- android:startOffset="0"
- android:valueFrom="M0 411 C19.33,411 35,426.67 35,446 C35,465.33 19.33,481 0,481 C-19.33,481 -35,465.33 -35,446 C-35,426.67 -19.33,411 0,411c "
- android:valueTo="M0 396 C27.61,396 50,418.39 50,446 C50,473.61 27.61,496 0,496 C-27.61,496 -50,473.61 -50,446 C-50,418.39 -27.61,396 0,396c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="433"
- android:propertyName="pathData"
- android:startOffset="217"
- android:valueFrom="M0 396 C27.61,396 50,418.39 50,446 C50,473.61 27.61,496 0,496 C-27.61,496 -50,473.61 -50,446 C-50,418.39 -27.61,396 0,396c "
- android:valueTo="M0 68 C27.61,68 50,90.39 50,118 C50,145.61 27.61,168 0,168 C-27.61,168 -50,145.61 -50,118 C-50,90.39 -27.61,68 0,68c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2167"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="1367"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group
- android:name="_R_G_L_3_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/fake_wallpaper_color_light_mode"
- android:fillType="nonZero"
- android:pathData=" M206 -446 C206,-446 206,446 206,446 C206,446 -206,446 -206,446 C-206,446 -206,-446 -206,-446 C-206,-446 206,-446 206,-446c " />
- </group>
- <group
- android:name="_R_G_L_2_G"
- android:scaleY="0">
- <group
- android:name="_R_G_L_2_G_L_4_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="776">
- <path
- android:name="_R_G_L_2_G_L_4_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#d9d9da"
- android:fillType="nonZero"
- android:pathData=" M180 0 C180,0 180,0 180,0 C180,13.8 168.8,25 155,25 C155,25 -155,25 -155,25 C-168.8,25 -180,13.8 -180,0 C-180,0 -180,0 -180,0 C-180,-13.8 -168.8,-25 -155,-25 C-155,-25 155,-25 155,-25 C168.8,-25 180,-13.8 180,0c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_3_G"
- android:scaleY="0"
- android:translateX="56"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_3_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#8ab4f8"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_2_G"
- android:scaleY="0"
- android:translateX="156"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_2_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#f28b82"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_1_G"
- android:scaleY="0"
- android:translateX="256"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_1_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#fdd663"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- <group
- android:name="_R_G_L_2_G_L_0_G"
- android:scaleY="0"
- android:translateX="356"
- android:translateY="673">
- <path
- android:name="_R_G_L_2_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#81c995"
- android:fillType="nonZero"
- android:pathData=" M0 -30 C16.56,-30 30,-16.56 30,0 C30,16.56 16.56,30 0,30 C-16.56,30 -30,16.56 -30,0 C-30,-16.56 -16.56,-30 0,-30c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_T_1"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="206"
- android:translateY="446">
- <group
- android:name="_R_G_L_1_G"
- android:translateX="-206"
- android:translateY="-446">
- <group android:name="_R_G_L_1_G_L_4_G">
- <group
- android:name="_R_G_L_1_G_L_4_G_L_11_G"
- android:scaleX="0.87473"
- android:scaleY="0.98643"
- android:translateX="206"
- android:translateY="472.769">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M235.5 -407 C235.5,-407 235.5,407 235.5,407 C235.5,416.93 227.43,425 217.5,425 C217.5,425 -217.5,425 -217.5,425 C-227.43,425 -235.5,416.93 -235.5,407 C-235.5,407 -235.5,-407 -235.5,-407 C-235.5,-416.93 -227.43,-425 -217.5,-425 C-217.5,-425 217.5,-425 217.5,-425 C227.43,-425 235.5,-416.93 235.5,-407c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_10_G"
- android:translateX="182.5"
- android:translateY="831">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_9_G"
- android:translateX="186"
- android:translateY="801">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -3 C162,-3 162,3 162,3 C162,7.42 158.42,11 154,11 C154,11 -154,11 -154,11 C-158.42,11 -162,7.42 -162,3 C-162,3 -162,-3 -162,-3 C-162,-7.42 -158.42,-11 -154,-11 C-154,-11 154,-11 154,-11 C158.42,-11 162,-7.42 162,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_8_G"
- android:translateX="119"
- android:translateY="755">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M95 -3 C95,-3 95,3 95,3 C95,7.42 91.42,11 87,11 C87,11 -87,11 -87,11 C-91.42,11 -95,7.42 -95,3 C-95,3 -95,-3 -95,-3 C-95,-7.42 -91.42,-11 -87,-11 C-87,-11 87,-11 87,-11 C91.42,-11 95,-7.42 95,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_7_G"
- android:translateX="182.5"
- android:translateY="725">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_6_G"
- android:translateX="197.5"
- android:translateY="695">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M173.5 -3 C173.5,-3 173.5,3 173.5,3 C173.5,7.42 169.92,11 165.5,11 C165.5,11 -165.5,11 -165.5,11 C-169.92,11 -173.5,7.42 -173.5,3 C-173.5,3 -173.5,-3 -173.5,-3 C-173.5,-7.42 -169.92,-11 -165.5,-11 C-165.5,-11 165.5,-11 165.5,-11 C169.92,-11 173.5,-7.42 173.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_5_G"
- android:translateX="192"
- android:translateY="665">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M168 -3 C168,-3 168,3 168,3 C168,7.42 164.42,11 160,11 C160,11 -160,11 -160,11 C-164.42,11 -168,7.42 -168,3 C-168,3 -168,-3 -168,-3 C-168,-7.42 -164.42,-11 -160,-11 C-160,-11 160,-11 160,-11 C164.42,-11 168,-7.42 168,-3c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_4_G"
- android:translateX="105.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_3_G"
- android:translateX="47.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_2_G"
- android:translateX="142.5"
- android:translateY="328">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M118.5 -10 C118.5,-10 118.5,10 118.5,10 C118.5,14.42 114.92,18 110.5,18 C110.5,18 -110.5,18 -110.5,18 C-114.92,18 -118.5,14.42 -118.5,10 C-118.5,10 -118.5,-10 -118.5,-10 C-118.5,-14.42 -114.92,-18 -110.5,-18 C-110.5,-18 110.5,-18 110.5,-18 C114.92,-18 118.5,-14.42 118.5,-10c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_1_G"
- android:translateX="186"
- android:translateY="284">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -10 C162,-10 162,10 162,10 C162,14.42 158.42,18 154,18 C154,18 -154,18 -154,18 C-158.42,18 -162,14.42 -162,10 C-162,10 -162,-10 -162,-10 C-162,-14.42 -158.42,-18 -154,-18 C-154,-18 154,-18 154,-18 C158.42,-18 162,-14.42 162,-10c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_4_G_L_0_G"
- android:translateX="155"
- android:translateY="240">
- <path
- android:name="_R_G_L_1_G_L_4_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M131 -10 C131,-10 131,10 131,10 C131,14.42 127.42,18 123,18 C123,18 -123,18 -123,18 C-127.42,18 -131,14.42 -131,10 C-131,10 -131,-10 -131,-10 C-131,-14.42 -127.42,-18 -123,-18 C-123,-18 123,-18 123,-18 C127.42,-18 131,-14.42 131,-10c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_L_3_G"
- android:translateX="24"
- android:translateY="390">
- <group
- android:name="_R_G_L_1_G_L_3_G_L_0_G"
- android:translateX="182"
- android:translateY="120">
- <path
- android:name="_R_G_L_1_G_L_3_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M182 -98 C182,-98 182,98 182,98 C182,110.14 172.14,120 160,120 C160,120 -160,120 -160,120 C-172.14,120 -182,110.14 -182,98 C-182,98 -182,-98 -182,-98 C-182,-110.14 -172.14,-120 -160,-120 C-160,-120 160,-120 160,-120 C172.14,-120 182,-110.14 182,-98c " />
- </group>
- </group>
- <group android:name="_R_G_L_1_G_L_2_G">
- <group
- android:name="_R_G_L_1_G_L_2_G_L_2_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_1_G_L_2_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -95.63 C206,-95.63 206,42.37 206,42.37 C206,43.47 205.1,44.37 204,44.37 C204,44.37 -204,44.37 -204,44.37 C-205.1,44.37 -206,43.47 -206,42.37 C-206,42.37 -206,-95.63 -206,-95.63 C-206,-96.73 -205.1,-97.63 -204,-97.63 C-204,-97.63 204,-97.63 204,-97.63 C205.1,-97.63 206,-96.73 206,-95.63c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_2_G_L_1_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_1_G_L_2_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M109 -14 C109,-14 109,14 109,14 C109,15.1 108.1,16 107,16 C107,16 -107,16 -107,16 C-108.1,16 -109,15.1 -109,14 C-109,14 -109,-14 -109,-14 C-109,-15.1 -108.1,-16 -107,-16 C-107,-16 107,-16 107,-16 C108.1,-16 109,-15.1 109,-14c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_2_G_L_0_G"
- android:translateX="46"
- android:translateY="145">
- <path
- android:name="_R_G_L_1_G_L_2_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M22 -14 C22,-14 22,14 22,14 C22,18.42 18.42,22 14,22 C14,22 -14,22 -14,22 C-18.42,22 -22,18.42 -22,14 C-22,14 -22,-14 -22,-14 C-22,-18.42 -18.42,-22 -14,-22 C-14,-22 14,-22 14,-22 C18.42,-22 22,-18.42 22,-14c " />
- </group>
- </group>
- <group android:name="_R_G_L_1_G_L_1_G">
- <group
- android:name="_R_G_L_1_G_L_1_G_L_2_G"
- android:translateX="206"
- android:translateY="51">
- <path
- android:name="_R_G_L_1_G_L_1_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#6e7175"
- android:fillType="nonZero"
- android:pathData=" M206 -0.27 C206,-0.27 206,49.73 206,49.73 C206,49.73 -206,49.73 -206,49.73 C-206,49.73 -206,-0.27 -206,-0.27 C-206,-0.27 206,-0.27 206,-0.27c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_1_G_L_1_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_1_G_L_1_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#6e7175"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_1_G_L_1_G_L_0_G"
- android:translateX="206"
- android:translateY="66.5">
- <path
- android:name="_R_G_L_1_G_L_1_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9a9a9a"
- android:fillType="nonZero"
- android:pathData=" M190 0 C190,0 190,0 190,0 C190,10.21 181.71,18.5 171.5,18.5 C171.5,18.5 -171.5,18.5 -171.5,18.5 C-181.71,18.5 -190,10.21 -190,0 C-190,0 -190,0 -190,0 C-190,-10.21 -181.71,-18.5 -171.5,-18.5 C-171.5,-18.5 171.5,-18.5 171.5,-18.5 C181.71,-18.5 190,-10.21 190,0c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_L_0_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_1_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bac4d6"
- android:fillType="nonZero"
- android:pathData=" M206.06 -430.06 C206.06,-430.06 206,431 206,431 C206,446 189.75,446 189.79,446 C189.79,446 -189.98,446 -189.98,446 C-189.94,446 -206,446 -206,431 C-206,431 -206,-430 -206,-430 C-206,-446 -189.97,-446 -190.01,-446 C-190.01,-446 188.98,-446.06 188.98,-446.06 C188.94,-446.06 206,-446 206.06,-430.06c " />
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#84ba69"
- android:fillType="nonZero"
- android:pathData=" M0 411 C19.33,411 35,426.67 35,446 C35,465.33 19.33,481 0,481 C-19.33,481 -35,465.33 -35,446 C-35,426.67 -19.33,411 0,411c " />
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml
deleted file mode 100644
index b007d20..0000000
--- a/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml
+++ /dev/null
@@ -1,1623 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="_R_G_L_4_G_N_3_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="1050"
- android:pathData="M 206,446C 206,446 206,395 206,395"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="217">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_4_G_N_3_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="1050"
- android:propertyName="scaleX"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.6"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="1050"
- android:propertyName="scaleY"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.6"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_4_G_N_3_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="3400"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_28_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_27_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_26_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_25_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_24_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_23_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_22_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_21_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_20_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_19_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_18_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_17_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_16_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_15_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_14_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_13_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_12_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_11_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_10_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_9_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_8_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_7_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:pathData="M 206,395C 206,403.5 206,437.5 206,446"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="2083">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleX"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleX"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleY"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="0.6"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleX"
- android:startOffset="2567"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleY"
- android:startOffset="2567"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:pathData="M 206,395C 206,403.5 206,437.5 206,446"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="2083">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleX"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleX"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleY"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2567"
- android:valueFrom="0"
- android:valueTo="0.6"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="250"
- android:pathData="M -556.176,-7.307C -556.176,-7.307 -421.176,-7.307 -421.176,-7.307"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="1350">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.272,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="417"
- android:pathData="M -421.176,-7.307C -421.176,-7.307 -429.51,-7.307 -429.51,-7.307"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="1600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:pathData="M 206,395C 206,403.5 206,437.5 206,446"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="2083">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleX"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleX"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleY"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="1350"
- android:valueFrom="0"
- android:valueTo="0.6"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="fillAlpha"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="1833"
- android:propertyName="fillAlpha"
- android:startOffset="217"
- android:valueFrom="0.75"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="2050"
- android:valueFrom="0.75"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="pathData"
- android:startOffset="0"
- android:valueFrom="M0 406 C21.54,406 39,423.46 39,445 C39,466.54 21.54,484 0,484 C-21.54,484 -39,466.54 -39,445 C-39,423.46 -21.54,406 0,406c "
- android:valueTo="M0 395 C27.61,395 50,417.39 50,445 C50,472.61 27.61,495 0,495 C-27.61,495 -50,472.61 -50,445 C-50,417.39 -27.61,395 0,395c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="1050"
- android:propertyName="pathData"
- android:startOffset="217"
- android:valueFrom="M0 395 C27.61,395 50,417.39 50,445 C50,472.61 27.61,495 0,495 C-27.61,495 -50,472.61 -50,445 C-50,417.39 -27.61,395 0,395c "
- android:valueTo="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="783"
- android:propertyName="pathData"
- android:startOffset="1267"
- android:valueFrom="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueTo="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="pathData"
- android:startOffset="2050"
- android:valueFrom="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueTo="M0 180 C19.88,180 36,196.12 36,216 C36,235.88 19.88,252 0,252 C-19.88,252 -36,235.88 -36,216 C-36,196.12 -19.88,180 0,180c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="2750"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group
- android:name="_R_G_L_5_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/fake_wallpaper_color_dark_mode"
- android:fillType="nonZero"
- android:pathData=" M206 -446 C206,-446 206,446 206,446 C206,446 -206,446 -206,446 C-206,446 -206,-446 -206,-446 C-206,-446 206,-446 206,-446c " />
- </group>
- <group
- android:name="_R_G_L_4_G_N_3_T_0"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="206"
- android:translateY="446">
- <group
- android:name="_R_G_L_4_G"
- android:translateX="-206"
- android:translateY="-446">
- <group android:name="_R_G_L_4_G_L_0_G">
- <group
- android:name="_R_G_L_4_G_L_0_G_L_28_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_28_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#000000"
- android:fillType="nonZero"
- android:pathData=" M206 -422 C206,-422 206,422 206,422 C206,435.25 195.25,446 182,446 C182,446 -182,446 -182,446 C-195.25,446 -206,435.25 -206,422 C-206,422 -206,-422 -206,-422 C-206,-435.25 -195.25,-446 -182,-446 C-182,-446 182,-446 182,-446 C195.25,-446 206,-435.25 206,-422c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_27_G"
- android:translateX="206"
- android:translateY="422.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_27_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -395.5 C206,-395.5 206,395.5 206,395.5 C206,395.5 -206,395.5 -206,395.5 C-206,395.5 -206,-395.5 -206,-395.5 C-206,-395.5 206,-395.5 206,-395.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_26_G"
- android:translateX="206"
- android:translateY="496.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_26_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -377.5 C206,-377.5 206,377.5 206,377.5 C206,387.43 197.93,395.5 188,395.5 C188,395.5 -188,395.5 -188,395.5 C-197.93,395.5 -206,387.43 -206,377.5 C-206,377.5 -206,-377.5 -206,-377.5 C-206,-387.43 -197.93,-395.5 -188,-395.5 C-188,-395.5 188,-395.5 188,-395.5 C197.93,-395.5 206,-387.43 206,-377.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_25_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_25_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -23.5 C206,-23.5 206,50.5 206,50.5 C206,50.5 -206,50.5 -206,50.5 C-206,50.5 -206,-23.5 -206,-23.5 C-206,-23.5 206,-23.5 206,-23.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_24_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_24_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_23_G"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_23_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_22_G"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_22_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_21_G"
- android:translateX="148.5"
- android:translateY="148">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_21_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M46.5 -5 C46.5,-5 46.5,5 46.5,5 C46.5,7.21 44.71,9 42.5,9 C42.5,9 -42.5,9 -42.5,9 C-44.71,9 -46.5,7.21 -46.5,5 C-46.5,5 -46.5,-5 -46.5,-5 C-46.5,-7.21 -44.71,-9 -42.5,-9 C-42.5,-9 42.5,-9 42.5,-9 C44.71,-9 46.5,-7.21 46.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_20_G"
- android:translateX="186"
- android:translateY="169">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_20_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M84 -4 C84,-4 84,4 84,4 C84,6.21 82.21,8 80,8 C80,8 -80,8 -80,8 C-82.21,8 -84,6.21 -84,4 C-84,4 -84,-4 -84,-4 C-84,-6.21 -82.21,-8 -80,-8 C-80,-8 80,-8 80,-8 C82.21,-8 84,-6.21 84,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_19_G"
- android:translateX="54"
- android:translateY="245">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_19_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_18_G"
- android:translateX="162"
- android:translateY="236">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_18_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60 -5 C60,-5 60,5 60,5 C60,7.21 58.21,9 56,9 C56,9 -56,9 -56,9 C-58.21,9 -60,7.21 -60,5 C-60,5 -60,-5 -60,-5 C-60,-7.21 -58.21,-9 -56,-9 C-56,-9 56,-9 56,-9 C58.21,-9 60,-7.21 60,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_17_G"
- android:translateX="171.5"
- android:translateY="257">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_17_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M69.5 -4 C69.5,-4 69.5,4 69.5,4 C69.5,6.21 67.71,8 65.5,8 C65.5,8 -65.5,8 -65.5,8 C-67.71,8 -69.5,6.21 -69.5,4 C-69.5,4 -69.5,-4 -69.5,-4 C-69.5,-6.21 -67.71,-8 -65.5,-8 C-65.5,-8 65.5,-8 65.5,-8 C67.71,-8 69.5,-6.21 69.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_16_G"
- android:translateX="54"
- android:translateY="333">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_16_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_15_G"
- android:translateX="158"
- android:translateY="324">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_15_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M56 -5 C56,-5 56,5 56,5 C56,7.21 54.21,9 52,9 C52,9 -52,9 -52,9 C-54.21,9 -56,7.21 -56,5 C-56,5 -56,-5 -56,-5 C-56,-7.21 -54.21,-9 -52,-9 C-52,-9 52,-9 52,-9 C54.21,-9 56,-7.21 56,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_14_G"
- android:translateX="217.5"
- android:translateY="345">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_14_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M115.5 -4 C115.5,-4 115.5,4 115.5,4 C115.5,6.21 113.71,8 111.5,8 C111.5,8 -111.5,8 -111.5,8 C-113.71,8 -115.5,6.21 -115.5,4 C-115.5,4 -115.5,-4 -115.5,-4 C-115.5,-6.21 -113.71,-8 -111.5,-8 C-111.5,-8 111.5,-8 111.5,-8 C113.71,-8 115.5,-6.21 115.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_13_G"
- android:translateX="54"
- android:translateY="421">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_13_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_12_G"
- android:translateX="170"
- android:translateY="412">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_12_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M68 -5 C68,-5 68,5 68,5 C68,7.21 66.21,9 64,9 C64,9 -64,9 -64,9 C-66.21,9 -68,7.21 -68,5 C-68,5 -68,-5 -68,-5 C-68,-7.21 -66.21,-9 -64,-9 C-64,-9 64,-9 64,-9 C66.21,-9 68,-7.21 68,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_11_G"
- android:translateX="198.5"
- android:translateY="433">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_10_G"
- android:translateX="54"
- android:translateY="509">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_9_G"
- android:translateX="135"
- android:translateY="500">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M33 -5 C33,-5 33,5 33,5 C33,7.21 31.21,9 29,9 C29,9 -29,9 -29,9 C-31.21,9 -33,7.21 -33,5 C-33,5 -33,-5 -33,-5 C-33,-7.21 -31.21,-9 -29,-9 C-29,-9 29,-9 29,-9 C31.21,-9 33,-7.21 33,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_8_G"
- android:translateX="185.5"
- android:translateY="521">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M83.5 -4 C83.5,-4 83.5,4 83.5,4 C83.5,6.21 81.71,8 79.5,8 C79.5,8 -79.5,8 -79.5,8 C-81.71,8 -83.5,6.21 -83.5,4 C-83.5,4 -83.5,-4 -83.5,-4 C-83.5,-6.21 -81.71,-8 -79.5,-8 C-79.5,-8 79.5,-8 79.5,-8 C81.71,-8 83.5,-6.21 83.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_7_G"
- android:translateX="54"
- android:translateY="597">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_6_G"
- android:translateX="168.5"
- android:translateY="588">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M66.5 -5 C66.5,-5 66.5,5 66.5,5 C66.5,7.21 64.71,9 62.5,9 C62.5,9 -62.5,9 -62.5,9 C-64.71,9 -66.5,7.21 -66.5,5 C-66.5,5 -66.5,-5 -66.5,-5 C-66.5,-7.21 -64.71,-9 -62.5,-9 C-62.5,-9 62.5,-9 62.5,-9 C64.71,-9 66.5,-7.21 66.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_5_G"
- android:translateX="198.5"
- android:translateY="609">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_4_G"
- android:translateX="54"
- android:translateY="685">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_3_G"
- android:translateX="162.5"
- android:translateY="676">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60.5 -5 C60.5,-5 60.5,5 60.5,5 C60.5,7.21 58.71,9 56.5,9 C56.5,9 -56.5,9 -56.5,9 C-58.71,9 -60.5,7.21 -60.5,5 C-60.5,5 -60.5,-5 -60.5,-5 C-60.5,-7.21 -58.71,-9 -56.5,-9 C-56.5,-9 56.5,-9 56.5,-9 C58.71,-9 60.5,-7.21 60.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_2_G"
- android:translateX="174"
- android:translateY="697">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M72 -4 C72,-4 72,4 72,4 C72,6.21 70.21,8 68,8 C68,8 -68,8 -68,8 C-70.21,8 -72,6.21 -72,4 C-72,4 -72,-4 -72,-4 C-72,-6.21 -70.21,-8 -68,-8 C-68,-8 68,-8 68,-8 C70.21,-8 72,-6.21 72,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_1_G"
- android:translateX="313.5"
- android:translateY="798">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M74.5 0 C74.5,0 74.5,0 74.5,0 C74.5,15.45 61.95,28 46.5,28 C46.5,28 -46.5,28 -46.5,28 C-61.95,28 -74.5,15.45 -74.5,0 C-74.5,0 -74.5,0 -74.5,0 C-74.5,-15.45 -61.95,-28 -46.5,-28 C-46.5,-28 46.5,-28 46.5,-28 C61.95,-28 74.5,-15.45 74.5,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_0_G"
- android:translateX="205.5"
- android:translateY="61">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#f8f9fa"
- android:fillType="nonZero"
- android:pathData=" M171.5 -14 C171.5,-14 171.5,14 171.5,14 C171.5,16.21 169.71,18 167.5,18 C167.5,18 -167.5,18 -167.5,18 C-169.71,18 -171.5,16.21 -171.5,14 C-171.5,14 -171.5,-14 -171.5,-14 C-171.5,-16.21 -169.71,-18 -167.5,-18 C-167.5,-18 167.5,-18 167.5,-18 C169.71,-18 171.5,-16.21 171.5,-14c " />
- </group>
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_3_G_N_2_T_0"
- android:scaleX="0.6"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="395">
- <group
- android:name="_R_G_L_3_G"
- android:translateX="-206"
- android:translateY="-446">
- <group
- android:name="_R_G_L_3_G_L_0_G"
- android:scaleY="0">
- <group
- android:name="_R_G_L_3_G_L_0_G_L_28_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_28_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#000000"
- android:fillType="nonZero"
- android:pathData=" M206 -422 C206,-422 206,422 206,422 C206,435.25 195.25,446 182,446 C182,446 -182,446 -182,446 C-195.25,446 -206,435.25 -206,422 C-206,422 -206,-422 -206,-422 C-206,-435.25 -195.25,-446 -182,-446 C-182,-446 182,-446 182,-446 C195.25,-446 206,-435.25 206,-422c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_27_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="422.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_27_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -395.5 C206,-395.5 206,395.5 206,395.5 C206,395.5 -206,395.5 -206,395.5 C-206,395.5 -206,-395.5 -206,-395.5 C-206,-395.5 206,-395.5 206,-395.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_26_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="496.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_26_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -377.5 C206,-377.5 206,377.5 206,377.5 C206,387.43 197.93,395.5 188,395.5 C188,395.5 -188,395.5 -188,395.5 C-197.93,395.5 -206,387.43 -206,377.5 C-206,377.5 -206,-377.5 -206,-377.5 C-206,-387.43 -197.93,-395.5 -188,-395.5 C-188,-395.5 188,-395.5 188,-395.5 C197.93,-395.5 206,-387.43 206,-377.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_25_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_25_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -23.5 C206,-23.5 206,50.5 206,50.5 C206,50.5 -206,50.5 -206,50.5 C-206,50.5 -206,-23.5 -206,-23.5 C-206,-23.5 206,-23.5 206,-23.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_24_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_24_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_23_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_23_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_22_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_22_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_21_G"
- android:scaleY="0"
- android:translateX="148.5"
- android:translateY="148">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_21_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M46.5 -5 C46.5,-5 46.5,5 46.5,5 C46.5,7.21 44.71,9 42.5,9 C42.5,9 -42.5,9 -42.5,9 C-44.71,9 -46.5,7.21 -46.5,5 C-46.5,5 -46.5,-5 -46.5,-5 C-46.5,-7.21 -44.71,-9 -42.5,-9 C-42.5,-9 42.5,-9 42.5,-9 C44.71,-9 46.5,-7.21 46.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_20_G"
- android:scaleY="0"
- android:translateX="186"
- android:translateY="169">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_20_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M84 -4 C84,-4 84,4 84,4 C84,6.21 82.21,8 80,8 C80,8 -80,8 -80,8 C-82.21,8 -84,6.21 -84,4 C-84,4 -84,-4 -84,-4 C-84,-6.21 -82.21,-8 -80,-8 C-80,-8 80,-8 80,-8 C82.21,-8 84,-6.21 84,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_19_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="245">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_19_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_18_G"
- android:scaleY="0"
- android:translateX="162"
- android:translateY="236">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_18_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60 -5 C60,-5 60,5 60,5 C60,7.21 58.21,9 56,9 C56,9 -56,9 -56,9 C-58.21,9 -60,7.21 -60,5 C-60,5 -60,-5 -60,-5 C-60,-7.21 -58.21,-9 -56,-9 C-56,-9 56,-9 56,-9 C58.21,-9 60,-7.21 60,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_17_G"
- android:scaleY="0"
- android:translateX="171.5"
- android:translateY="257">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_17_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M69.5 -4 C69.5,-4 69.5,4 69.5,4 C69.5,6.21 67.71,8 65.5,8 C65.5,8 -65.5,8 -65.5,8 C-67.71,8 -69.5,6.21 -69.5,4 C-69.5,4 -69.5,-4 -69.5,-4 C-69.5,-6.21 -67.71,-8 -65.5,-8 C-65.5,-8 65.5,-8 65.5,-8 C67.71,-8 69.5,-6.21 69.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_16_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="333">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_16_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_15_G"
- android:scaleY="0"
- android:translateX="158"
- android:translateY="324">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_15_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M56 -5 C56,-5 56,5 56,5 C56,7.21 54.21,9 52,9 C52,9 -52,9 -52,9 C-54.21,9 -56,7.21 -56,5 C-56,5 -56,-5 -56,-5 C-56,-7.21 -54.21,-9 -52,-9 C-52,-9 52,-9 52,-9 C54.21,-9 56,-7.21 56,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_14_G"
- android:scaleY="0"
- android:translateX="217.5"
- android:translateY="345">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_14_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M115.5 -4 C115.5,-4 115.5,4 115.5,4 C115.5,6.21 113.71,8 111.5,8 C111.5,8 -111.5,8 -111.5,8 C-113.71,8 -115.5,6.21 -115.5,4 C-115.5,4 -115.5,-4 -115.5,-4 C-115.5,-6.21 -113.71,-8 -111.5,-8 C-111.5,-8 111.5,-8 111.5,-8 C113.71,-8 115.5,-6.21 115.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_13_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="421">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_13_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_12_G"
- android:scaleY="0"
- android:translateX="170"
- android:translateY="412">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_12_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M68 -5 C68,-5 68,5 68,5 C68,7.21 66.21,9 64,9 C64,9 -64,9 -64,9 C-66.21,9 -68,7.21 -68,5 C-68,5 -68,-5 -68,-5 C-68,-7.21 -66.21,-9 -64,-9 C-64,-9 64,-9 64,-9 C66.21,-9 68,-7.21 68,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_11_G"
- android:scaleY="0"
- android:translateX="198.5"
- android:translateY="433">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_10_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="509">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_9_G"
- android:scaleY="0"
- android:translateX="135"
- android:translateY="500">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M33 -5 C33,-5 33,5 33,5 C33,7.21 31.21,9 29,9 C29,9 -29,9 -29,9 C-31.21,9 -33,7.21 -33,5 C-33,5 -33,-5 -33,-5 C-33,-7.21 -31.21,-9 -29,-9 C-29,-9 29,-9 29,-9 C31.21,-9 33,-7.21 33,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_8_G"
- android:scaleY="0"
- android:translateX="185.5"
- android:translateY="521">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M83.5 -4 C83.5,-4 83.5,4 83.5,4 C83.5,6.21 81.71,8 79.5,8 C79.5,8 -79.5,8 -79.5,8 C-81.71,8 -83.5,6.21 -83.5,4 C-83.5,4 -83.5,-4 -83.5,-4 C-83.5,-6.21 -81.71,-8 -79.5,-8 C-79.5,-8 79.5,-8 79.5,-8 C81.71,-8 83.5,-6.21 83.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_7_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="597">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_6_G"
- android:scaleY="0"
- android:translateX="168.5"
- android:translateY="588">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M66.5 -5 C66.5,-5 66.5,5 66.5,5 C66.5,7.21 64.71,9 62.5,9 C62.5,9 -62.5,9 -62.5,9 C-64.71,9 -66.5,7.21 -66.5,5 C-66.5,5 -66.5,-5 -66.5,-5 C-66.5,-7.21 -64.71,-9 -62.5,-9 C-62.5,-9 62.5,-9 62.5,-9 C64.71,-9 66.5,-7.21 66.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_5_G"
- android:scaleY="0"
- android:translateX="198.5"
- android:translateY="609">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_4_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="685">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_3_G"
- android:scaleY="0"
- android:translateX="162.5"
- android:translateY="676">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60.5 -5 C60.5,-5 60.5,5 60.5,5 C60.5,7.21 58.71,9 56.5,9 C56.5,9 -56.5,9 -56.5,9 C-58.71,9 -60.5,7.21 -60.5,5 C-60.5,5 -60.5,-5 -60.5,-5 C-60.5,-7.21 -58.71,-9 -56.5,-9 C-56.5,-9 56.5,-9 56.5,-9 C58.71,-9 60.5,-7.21 60.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_2_G"
- android:scaleY="0"
- android:translateX="174"
- android:translateY="697">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M72 -4 C72,-4 72,4 72,4 C72,6.21 70.21,8 68,8 C68,8 -68,8 -68,8 C-70.21,8 -72,6.21 -72,4 C-72,4 -72,-4 -72,-4 C-72,-6.21 -70.21,-8 -68,-8 C-68,-8 68,-8 68,-8 C70.21,-8 72,-6.21 72,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_1_G"
- android:scaleY="0"
- android:translateX="313.5"
- android:translateY="798">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M74.5 0 C74.5,0 74.5,0 74.5,0 C74.5,15.45 61.95,28 46.5,28 C46.5,28 -46.5,28 -46.5,28 C-61.95,28 -74.5,15.45 -74.5,0 C-74.5,0 -74.5,0 -74.5,0 C-74.5,-15.45 -61.95,-28 -46.5,-28 C-46.5,-28 46.5,-28 46.5,-28 C61.95,-28 74.5,-15.45 74.5,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_0_G"
- android:scaleY="0"
- android:translateX="205.5"
- android:translateY="61">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#f8f9fa"
- android:fillType="nonZero"
- android:pathData=" M171.5 -14 C171.5,-14 171.5,14 171.5,14 C171.5,16.21 169.71,18 167.5,18 C167.5,18 -167.5,18 -167.5,18 C-169.71,18 -171.5,16.21 -171.5,14 C-171.5,14 -171.5,-14 -171.5,-14 C-171.5,-16.21 -169.71,-18 -167.5,-18 C-167.5,-18 167.5,-18 167.5,-18 C169.71,-18 171.5,-16.21 171.5,-14c " />
- </group>
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_2_G_N_2_T_0"
- android:scaleX="0.6"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="395">
- <group
- android:name="_R_G_L_2_G"
- android:scaleX="1.3767699999999998"
- android:scaleY="1.3767699999999998"
- android:translateY="-508.163">
- <group
- android:name="_R_G_L_2_G_D_0_P_0_G_0_T_0"
- android:scaleX="0"
- android:scaleY="0">
- <path
- android:name="_R_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M0 25 C13.81,25 25,13.81 25,0 C25,-13.81 13.81,-25 0,-25 C-13.81,-25 -25,-13.81 -25,0 C-25,13.81 -13.81,25 0,25c " />
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_N_2_T_0"
- android:scaleX="0.6"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="395">
- <group
- android:name="_R_G_L_1_G"
- android:scaleX="1.39"
- android:scaleY="1.39"
- android:translateX="-556.176"
- android:translateY="-7.307">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/gesture_tutorial_fake_previous_task_view_color"
- android:fillType="nonZero"
- android:pathData=" M135 -301 C135,-301 135,311 135,311 C135,319.28 128.28,326 120,326 C120,326 -120,326 -120,326 C-128.28,326 -135,319.28 -135,311 C-135,311 -135,-301 -135,-301 C-135,-309.28 -128.28,-316 -120,-316 C-120,-316 120,-316 120,-316 C128.28,-316 135,-309.28 135,-301c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="@color/gesture_tutorial_primary_color"
- android:fillType="nonZero"
- android:pathData=" M0 406 C21.54,406 39,423.46 39,445 C39,466.54 21.54,484 0,484 C-21.54,484 -39,466.54 -39,445 C-39,423.46 -21.54,406 0,406c " />
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/res/drawable/gesture_tutorial_ripple.xml b/quickstep/res/drawable/gesture_tutorial_ripple.xml
similarity index 100%
rename from res/drawable/gesture_tutorial_ripple.xml
rename to quickstep/res/drawable/gesture_tutorial_ripple.xml
diff --git a/quickstep/res/drawable/mock_conversation.xml b/quickstep/res/drawable/mock_conversation.xml
deleted file mode 100644
index 272d9ed..0000000
--- a/quickstep/res/drawable/mock_conversation.xml
+++ /dev/null
@@ -1,212 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group
- android:name="_R_G_L_1_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -446 C206,-446 206,446 206,446 C206,446 -206,446 -206,446 C-206,446 -206,-446 -206,-446 C-206,-446 206,-446 206,-446c " />
- </group>
- <group
- android:name="_R_G_L_0_G"
- android:pivotX="206"
- android:pivotY="446"
- android:scaleX="1"
- android:scaleY="1">
- <group android:name="_R_G_L_0_G_L_0_G">
- <group
- android:name="_R_G_L_0_G_L_0_G_L_14_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_14_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#000000"
- android:fillType="nonZero"
- android:pathData=" M206 -422 C206,-422 206,422 206,422 C206,435.25 195.25,446 182,446 C182,446 -182,446 -182,446 C-195.25,446 -206,435.25 -206,422 C-206,422 -206,-422 -206,-422 C-206,-435.25 -195.25,-446 -182,-446 C-182,-446 182,-446 182,-446 C195.25,-446 206,-435.25 206,-422c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_13_G"
- android:translateX="206"
- android:translateY="496.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_13_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#f1f3f4"
- android:fillType="nonZero"
- android:pathData=" M206 -395.5 C206,-395.5 206,395.5 206,395.5 C206,395.5 -206,395.5 -206,395.5 C-206,395.5 -206,-395.5 -206,-395.5 C-206,-395.5 206,-395.5 206,-395.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_12_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_12_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -50.5 C206,-50.5 206,50.5 206,50.5 C206,50.5 -206,50.5 -206,50.5 C-206,50.5 -206,-50.5 -206,-50.5 C-206,-50.5 206,-50.5 206,-50.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_11_G"
- android:translateX="206"
- android:translateY="804">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M177 0 C177,12.15 167.15,22 155,22 C155,22 -155,22 -155,22 C-167.15,22 -177,12.15 -177,0 C-177,-12.15 -167.15,-22 -155,-22 C-155,-22 155,-22 155,-22 C167.15,-22 177,-12.15 177,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_10_G"
- android:translateX="117.5"
- android:translateY="61">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M83.5 -14 C83.5,-14 83.5,14 83.5,14 C83.5,16.21 81.71,18 79.5,18 C79.5,18 -79.5,18 -79.5,18 C-81.71,18 -83.5,16.21 -83.5,14 C-83.5,14 -83.5,-14 -83.5,-14 C-83.5,-16.21 -81.71,-18 -79.5,-18 C-79.5,-18 79.5,-18 79.5,-18 C81.71,-18 83.5,-16.21 83.5,-14c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_9_G"
- android:translateX="370"
- android:translateY="61">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M18 -14 C18,-14 18,14 18,14 C18,16.21 16.21,18 14,18 C14,18 -14,18 -14,18 C-16.21,18 -18,16.21 -18,14 C-18,14 -18,-14 -18,-14 C-18,-16.21 -16.21,-18 -14,-18 C-14,-18 14,-18 14,-18 C16.21,-18 18,-16.21 18,-14c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_8_G"
- android:translateX="318"
- android:translateY="61">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M18 -14 C18,-14 18,14 18,14 C18,16.21 16.21,18 14,18 C14,18 -14,18 -14,18 C-16.21,18 -18,16.21 -18,14 C-18,14 -18,-14 -18,-14 C-18,-16.21 -16.21,-18 -14,-18 C-14,-18 14,-18 14,-18 C16.21,-18 18,-16.21 18,-14c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_7_G"
- android:translateX="48"
- android:translateY="618">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M22 0 C22,12.15 12.15,22 0,22 C-12.15,22 -22,12.15 -22,0 C-22,-12.15 -12.15,-22 0,-22 C12.15,-22 22,-12.15 22,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_6_G"
- android:translateX="48"
- android:translateY="396">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M22 0 C22,12.15 12.15,22 0,22 C-12.15,22 -22,12.15 -22,0 C-22,-12.15 -12.15,-22 0,-22 C12.15,-22 22,-12.15 22,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_5_G"
- android:translateX="259"
- android:translateY="286">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M135 -38 C135,-38 135,38 135,38 C135,47.94 126.94,56 117,56 C117,56 -117,56 -117,56 C-126.94,56 -135,47.94 -135,38 C-135,38 -135,-38 -135,-38 C-135,-47.94 -126.94,-56 -117,-56 C-117,-56 117,-56 117,-56 C126.94,-56 135,-47.94 135,-38c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_4_G"
- android:translateX="259"
- android:translateY="468">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M134.5 4 C134.5,4 134.5,14 134.5,14 C134.5,16.21 132.71,18 130.5,18 C130.5,18 44.5,18 44.5,18 C42.29,18 40.5,16.21 40.5,14 C40.5,14 40.5,4 40.5,4 C40.5,1.79 42.29,0 44.5,0 C44.5,0 130.5,0 130.5,0 C132.71,0 134.5,1.79 134.5,4c M135 0 C135,9.66 127.17,17.5 117.5,17.5 C117.5,17.5 31,17.5 31,17.5 C21.34,17.5 13.5,9.66 13.5,0 C13.5,-9.66 21.34,-17.5 31,-17.5 C31,-17.5 117.5,-17.5 117.5,-17.5 C127.17,-17.5 135,-9.66 135,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_3_G"
- android:translateX="259"
- android:translateY="526.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M135 -32.5 C135,-32.5 135,20.5 135,20.5 C135,22.71 133.21,24.5 131,24.5 C131,24.5 -95,24.5 -95,24.5 C-97.21,24.5 -99,22.71 -99,20.5 C-99,20.5 -99,-32.5 -99,-32.5 C-99,-34.71 -97.21,-36.5 -95,-36.5 C-95,-36.5 131,-36.5 131,-36.5 C133.21,-36.5 135,-34.71 135,-32.5c M135 -18.5 C135,-18.5 135,18.5 135,18.5 C135,28.44 126.94,36.5 117,36.5 C117,36.5 -117,36.5 -117,36.5 C-126.94,36.5 -135,28.44 -135,18.5 C-135,18.5 -135,-18.5 -135,-18.5 C-135,-28.44 -126.94,-36.5 -117,-36.5 C-117,-36.5 117,-36.5 117,-36.5 C126.94,-36.5 135,-28.44 135,-18.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_2_G"
- android:translateX="259"
- android:translateY="708.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M135 -18.5 C135,-18.5 135,18.5 135,18.5 C135,28.44 126.94,36.5 117,36.5 C117,36.5 -117,36.5 -117,36.5 C-126.94,36.5 -135,28.44 -135,18.5 C-135,18.5 -135,-18.5 -135,-18.5 C-135,-28.44 -126.94,-36.5 -117,-36.5 C-117,-36.5 117,-36.5 117,-36.5 C126.94,-36.5 135,-28.44 135,-18.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_1_G"
- android:translateX="222"
- android:translateY="617">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M45.5 0 C45.5,9.66 37.67,17.5 28,17.5 C28,17.5 -117.5,17.5 -117.5,17.5 C-127.16,17.5 -135,9.66 -135,0 C-135,-9.66 -127.16,-17.5 -117.5,-17.5 C-117.5,-17.5 28,-17.5 28,-17.5 C37.67,-17.5 45.5,-9.66 45.5,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_0_G"
- android:translateX="222"
- android:translateY="395.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M77 0 C77,9.66 69.16,17.5 59.5,17.5 C59.5,17.5 -117.5,17.5 -117.5,17.5 C-127.16,17.5 -135,9.66 -135,0 C-135,-9.66 -127.16,-17.5 -117.5,-17.5 C-117.5,-17.5 59.5,-17.5 59.5,-17.5 C69.16,-17.5 77,-9.66 77,0c " />
- </group>
- </group>
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/mock_conversations_list.xml b/quickstep/res/drawable/mock_conversations_list.xml
deleted file mode 100644
index 2dbc88f..0000000
--- a/quickstep/res/drawable/mock_conversations_list.xml
+++ /dev/null
@@ -1,361 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group
- android:name="_R_G_L_1_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -446 C206,-446 206,446 206,446 C206,446 -206,446 -206,446 C-206,446 -206,-446 -206,-446 C-206,-446 206,-446 206,-446c " />
- </group>
- <group android:name="_R_G_L_0_G">
- <group android:name="_R_G_L_0_G_L_0_G">
- <group
- android:name="_R_G_L_0_G_L_0_G_L_28_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_28_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#000000"
- android:fillType="nonZero"
- android:pathData=" M206 -422 C206,-422 206,422 206,422 C206,435.25 195.25,446 182,446 C182,446 -182,446 -182,446 C-195.25,446 -206,435.25 -206,422 C-206,422 -206,-422 -206,-422 C-206,-435.25 -195.25,-446 -182,-446 C-182,-446 182,-446 182,-446 C195.25,-446 206,-435.25 206,-422c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_27_G"
- android:translateX="206"
- android:translateY="422.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_27_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -395.5 C206,-395.5 206,395.5 206,395.5 C206,395.5 -206,395.5 -206,395.5 C-206,395.5 -206,-395.5 -206,-395.5 C-206,-395.5 206,-395.5 206,-395.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_26_G"
- android:translateX="206"
- android:translateY="496.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_26_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -377.5 C206,-377.5 206,377.5 206,377.5 C206,387.43 197.93,395.5 188,395.5 C188,395.5 -188,395.5 -188,395.5 C-197.93,395.5 -206,387.43 -206,377.5 C-206,377.5 -206,-377.5 -206,-377.5 C-206,-387.43 -197.93,-395.5 -188,-395.5 C-188,-395.5 188,-395.5 188,-395.5 C197.93,-395.5 206,-387.43 206,-377.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_25_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_25_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -23.5 C206,-23.5 206,50.5 206,50.5 C206,50.5 -206,50.5 -206,50.5 C-206,50.5 -206,-23.5 -206,-23.5 C-206,-23.5 206,-23.5 206,-23.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_24_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_24_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_23_G"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_23_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_22_G"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_22_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_21_G"
- android:translateX="148.5"
- android:translateY="148">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_21_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M46.5 -5 C46.5,-5 46.5,5 46.5,5 C46.5,7.21 44.71,9 42.5,9 C42.5,9 -42.5,9 -42.5,9 C-44.71,9 -46.5,7.21 -46.5,5 C-46.5,5 -46.5,-5 -46.5,-5 C-46.5,-7.21 -44.71,-9 -42.5,-9 C-42.5,-9 42.5,-9 42.5,-9 C44.71,-9 46.5,-7.21 46.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_20_G"
- android:translateX="186"
- android:translateY="169">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_20_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M84 -4 C84,-4 84,4 84,4 C84,6.21 82.21,8 80,8 C80,8 -80,8 -80,8 C-82.21,8 -84,6.21 -84,4 C-84,4 -84,-4 -84,-4 C-84,-6.21 -82.21,-8 -80,-8 C-80,-8 80,-8 80,-8 C82.21,-8 84,-6.21 84,-4c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_19_G"
- android:translateX="54"
- android:translateY="245">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_19_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_18_G"
- android:translateX="162"
- android:translateY="236">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_18_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60 -5 C60,-5 60,5 60,5 C60,7.21 58.21,9 56,9 C56,9 -56,9 -56,9 C-58.21,9 -60,7.21 -60,5 C-60,5 -60,-5 -60,-5 C-60,-7.21 -58.21,-9 -56,-9 C-56,-9 56,-9 56,-9 C58.21,-9 60,-7.21 60,-5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_17_G"
- android:translateX="171.5"
- android:translateY="257">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_17_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M69.5 -4 C69.5,-4 69.5,4 69.5,4 C69.5,6.21 67.71,8 65.5,8 C65.5,8 -65.5,8 -65.5,8 C-67.71,8 -69.5,6.21 -69.5,4 C-69.5,4 -69.5,-4 -69.5,-4 C-69.5,-6.21 -67.71,-8 -65.5,-8 C-65.5,-8 65.5,-8 65.5,-8 C67.71,-8 69.5,-6.21 69.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_16_G"
- android:translateX="54"
- android:translateY="333">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_16_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_15_G"
- android:translateX="158"
- android:translateY="324">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_15_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M56 -5 C56,-5 56,5 56,5 C56,7.21 54.21,9 52,9 C52,9 -52,9 -52,9 C-54.21,9 -56,7.21 -56,5 C-56,5 -56,-5 -56,-5 C-56,-7.21 -54.21,-9 -52,-9 C-52,-9 52,-9 52,-9 C54.21,-9 56,-7.21 56,-5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_14_G"
- android:translateX="217.5"
- android:translateY="345">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_14_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M115.5 -4 C115.5,-4 115.5,4 115.5,4 C115.5,6.21 113.71,8 111.5,8 C111.5,8 -111.5,8 -111.5,8 C-113.71,8 -115.5,6.21 -115.5,4 C-115.5,4 -115.5,-4 -115.5,-4 C-115.5,-6.21 -113.71,-8 -111.5,-8 C-111.5,-8 111.5,-8 111.5,-8 C113.71,-8 115.5,-6.21 115.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_13_G"
- android:translateX="54"
- android:translateY="421">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_13_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_12_G"
- android:translateX="170"
- android:translateY="412">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_12_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M68 -5 C68,-5 68,5 68,5 C68,7.21 66.21,9 64,9 C64,9 -64,9 -64,9 C-66.21,9 -68,7.21 -68,5 C-68,5 -68,-5 -68,-5 C-68,-7.21 -66.21,-9 -64,-9 C-64,-9 64,-9 64,-9 C66.21,-9 68,-7.21 68,-5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_11_G"
- android:translateX="198.5"
- android:translateY="433">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_10_G"
- android:translateX="54"
- android:translateY="509">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_9_G"
- android:translateX="135"
- android:translateY="500">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M33 -5 C33,-5 33,5 33,5 C33,7.21 31.21,9 29,9 C29,9 -29,9 -29,9 C-31.21,9 -33,7.21 -33,5 C-33,5 -33,-5 -33,-5 C-33,-7.21 -31.21,-9 -29,-9 C-29,-9 29,-9 29,-9 C31.21,-9 33,-7.21 33,-5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_8_G"
- android:translateX="185.5"
- android:translateY="521">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M83.5 -4 C83.5,-4 83.5,4 83.5,4 C83.5,6.21 81.71,8 79.5,8 C79.5,8 -79.5,8 -79.5,8 C-81.71,8 -83.5,6.21 -83.5,4 C-83.5,4 -83.5,-4 -83.5,-4 C-83.5,-6.21 -81.71,-8 -79.5,-8 C-79.5,-8 79.5,-8 79.5,-8 C81.71,-8 83.5,-6.21 83.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_7_G"
- android:translateX="54"
- android:translateY="597">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_6_G"
- android:translateX="168.5"
- android:translateY="588">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M66.5 -5 C66.5,-5 66.5,5 66.5,5 C66.5,7.21 64.71,9 62.5,9 C62.5,9 -62.5,9 -62.5,9 C-64.71,9 -66.5,7.21 -66.5,5 C-66.5,5 -66.5,-5 -66.5,-5 C-66.5,-7.21 -64.71,-9 -62.5,-9 C-62.5,-9 62.5,-9 62.5,-9 C64.71,-9 66.5,-7.21 66.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_5_G"
- android:translateX="198.5"
- android:translateY="609">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_4_G"
- android:translateX="54"
- android:translateY="685">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_3_G"
- android:translateX="162.5"
- android:translateY="676">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60.5 -5 C60.5,-5 60.5,5 60.5,5 C60.5,7.21 58.71,9 56.5,9 C56.5,9 -56.5,9 -56.5,9 C-58.71,9 -60.5,7.21 -60.5,5 C-60.5,5 -60.5,-5 -60.5,-5 C-60.5,-7.21 -58.71,-9 -56.5,-9 C-56.5,-9 56.5,-9 56.5,-9 C58.71,-9 60.5,-7.21 60.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_2_G"
- android:translateX="174"
- android:translateY="697">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M72 -4 C72,-4 72,4 72,4 C72,6.21 70.21,8 68,8 C68,8 -68,8 -68,8 C-70.21,8 -72,6.21 -72,4 C-72,4 -72,-4 -72,-4 C-72,-6.21 -70.21,-8 -68,-8 C-68,-8 68,-8 68,-8 C70.21,-8 72,-6.21 72,-4c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_1_G"
- android:translateX="313.5"
- android:translateY="798">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M74.5 0 C74.5,0 74.5,0 74.5,0 C74.5,15.45 61.95,28 46.5,28 C46.5,28 -46.5,28 -46.5,28 C-61.95,28 -74.5,15.45 -74.5,0 C-74.5,0 -74.5,0 -74.5,0 C-74.5,-15.45 -61.95,-28 -46.5,-28 C-46.5,-28 46.5,-28 46.5,-28 C61.95,-28 74.5,-15.45 74.5,0c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_0_G"
- android:translateX="205.5"
- android:translateY="61">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#f8f9fa"
- android:fillType="nonZero"
- android:pathData=" M171.5 -14 C171.5,-14 171.5,14 171.5,14 C171.5,16.21 169.71,18 167.5,18 C167.5,18 -167.5,18 -167.5,18 C-169.71,18 -171.5,16.21 -171.5,14 C-171.5,14 -171.5,-14 -171.5,-14 C-171.5,-16.21 -169.71,-18 -167.5,-18 C-167.5,-18 167.5,-18 167.5,-18 C169.71,-18 171.5,-16.21 171.5,-14c " />
- </group>
- </group>
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/mock_webpage_dark_mode.xml b/quickstep/res/drawable/mock_webpage_dark_mode.xml
deleted file mode 100644
index 93b22b7..0000000
--- a/quickstep/res/drawable/mock_webpage_dark_mode.xml
+++ /dev/null
@@ -1,251 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group android:name="_R_G_L_0_G">
- <group android:name="_R_G_L_0_G_L_3_G">
- <group
- android:name="_R_G_L_0_G_L_3_G_L_11_G"
- android:scaleX="0.87473"
- android:scaleY="0.98643"
- android:translateX="206"
- android:translateY="472.769">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M235.5 -407 C235.5,-407 235.5,407 235.5,407 C235.5,416.93 227.43,425 217.5,425 C217.5,425 -217.5,425 -217.5,425 C-227.43,425 -235.5,416.93 -235.5,407 C-235.5,407 -235.5,-407 -235.5,-407 C-235.5,-416.93 -227.43,-425 -217.5,-425 C-217.5,-425 217.5,-425 217.5,-425 C227.43,-425 235.5,-416.93 235.5,-407c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_10_G"
- android:translateX="182.5"
- android:translateY="831">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_9_G"
- android:translateX="186"
- android:translateY="801">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -3 C162,-3 162,3 162,3 C162,7.42 158.42,11 154,11 C154,11 -154,11 -154,11 C-158.42,11 -162,7.42 -162,3 C-162,3 -162,-3 -162,-3 C-162,-7.42 -158.42,-11 -154,-11 C-154,-11 154,-11 154,-11 C158.42,-11 162,-7.42 162,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_8_G"
- android:translateX="119"
- android:translateY="755">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M95 -3 C95,-3 95,3 95,3 C95,7.42 91.42,11 87,11 C87,11 -87,11 -87,11 C-91.42,11 -95,7.42 -95,3 C-95,3 -95,-3 -95,-3 C-95,-7.42 -91.42,-11 -87,-11 C-87,-11 87,-11 87,-11 C91.42,-11 95,-7.42 95,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_7_G"
- android:translateX="182.5"
- android:translateY="725">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_6_G"
- android:translateX="197.5"
- android:translateY="695">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M173.5 -3 C173.5,-3 173.5,3 173.5,3 C173.5,7.42 169.92,11 165.5,11 C165.5,11 -165.5,11 -165.5,11 C-169.92,11 -173.5,7.42 -173.5,3 C-173.5,3 -173.5,-3 -173.5,-3 C-173.5,-7.42 -169.92,-11 -165.5,-11 C-165.5,-11 165.5,-11 165.5,-11 C169.92,-11 173.5,-7.42 173.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_5_G"
- android:translateX="192"
- android:translateY="665">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M168 -3 C168,-3 168,3 168,3 C168,7.42 164.42,11 160,11 C160,11 -160,11 -160,11 C-164.42,11 -168,7.42 -168,3 C-168,3 -168,-3 -168,-3 C-168,-7.42 -164.42,-11 -160,-11 C-160,-11 160,-11 160,-11 C164.42,-11 168,-7.42 168,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_4_G"
- android:translateX="105.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_3_G"
- android:translateX="47.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_2_G"
- android:translateX="142.5"
- android:translateY="328">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M118.5 -10 C118.5,-10 118.5,10 118.5,10 C118.5,14.42 114.92,18 110.5,18 C110.5,18 -110.5,18 -110.5,18 C-114.92,18 -118.5,14.42 -118.5,10 C-118.5,10 -118.5,-10 -118.5,-10 C-118.5,-14.42 -114.92,-18 -110.5,-18 C-110.5,-18 110.5,-18 110.5,-18 C114.92,-18 118.5,-14.42 118.5,-10c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_1_G"
- android:translateX="186"
- android:translateY="284">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -10 C162,-10 162,10 162,10 C162,14.42 158.42,18 154,18 C154,18 -154,18 -154,18 C-158.42,18 -162,14.42 -162,10 C-162,10 -162,-10 -162,-10 C-162,-14.42 -158.42,-18 -154,-18 C-154,-18 154,-18 154,-18 C158.42,-18 162,-14.42 162,-10c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G_L_0_G"
- android:translateX="155"
- android:translateY="240">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M131 -10 C131,-10 131,10 131,10 C131,14.42 127.42,18 123,18 C123,18 -123,18 -123,18 C-127.42,18 -131,14.42 -131,10 C-131,10 -131,-10 -131,-10 C-131,-14.42 -127.42,-18 -123,-18 C-123,-18 123,-18 123,-18 C127.42,-18 131,-14.42 131,-10c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G_L_2_G"
- android:translateX="24"
- android:translateY="390">
- <group
- android:name="_R_G_L_0_G_L_2_G_L_0_G"
- android:translateX="182"
- android:translateY="120">
- <path
- android:name="_R_G_L_0_G_L_2_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M182 -98 C182,-98 182,98 182,98 C182,110.14 172.14,120 160,120 C160,120 -160,120 -160,120 C-172.14,120 -182,110.14 -182,98 C-182,98 -182,-98 -182,-98 C-182,-110.14 -172.14,-120 -160,-120 C-160,-120 160,-120 160,-120 C172.14,-120 182,-110.14 182,-98c " />
- </group>
- </group>
- <group android:name="_R_G_L_0_G_L_1_G">
- <group
- android:name="_R_G_L_0_G_L_1_G_L_2_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_0_G_L_1_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -95.63 C206,-95.63 206,42.37 206,42.37 C206,43.47 205.1,44.37 204,44.37 C204,44.37 -204,44.37 -204,44.37 C-205.1,44.37 -206,43.47 -206,42.37 C-206,42.37 -206,-95.63 -206,-95.63 C-206,-96.73 -205.1,-97.63 -204,-97.63 C-204,-97.63 204,-97.63 204,-97.63 C205.1,-97.63 206,-96.73 206,-95.63c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_1_G_L_1_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_0_G_L_1_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M109 -14 C109,-14 109,14 109,14 C109,15.1 108.1,16 107,16 C107,16 -107,16 -107,16 C-108.1,16 -109,15.1 -109,14 C-109,14 -109,-14 -109,-14 C-109,-15.1 -108.1,-16 -107,-16 C-107,-16 107,-16 107,-16 C108.1,-16 109,-15.1 109,-14c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_1_G_L_0_G"
- android:translateX="46"
- android:translateY="145">
- <path
- android:name="_R_G_L_0_G_L_1_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M22 -14 C22,-14 22,14 22,14 C22,18.42 18.42,22 14,22 C14,22 -14,22 -14,22 C-18.42,22 -22,18.42 -22,14 C-22,14 -22,-14 -22,-14 C-22,-18.42 -18.42,-22 -14,-22 C-14,-22 14,-22 14,-22 C18.42,-22 22,-18.42 22,-14c " />
- </group>
- </group>
- <group android:name="_R_G_L_0_G_L_0_G">
- <group
- android:name="_R_G_L_0_G_L_0_G_L_2_G"
- android:translateX="206"
- android:translateY="51">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#202124"
- android:fillType="nonZero"
- android:pathData=" M206 -0.27 C206,-0.27 206,49.73 206,49.73 C206,49.73 -206,49.73 -206,49.73 C-206,49.73 -206,-0.27 -206,-0.27 C-206,-0.27 206,-0.27 206,-0.27c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_1_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#202124"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G_L_0_G"
- android:translateX="206"
- android:translateY="66.5">
- <path
- android:name="_R_G_L_0_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#3c4043"
- android:fillType="nonZero"
- android:pathData=" M190 0 C190,0 190,0 190,0 C190,10.21 181.71,18.5 171.5,18.5 C171.5,18.5 -171.5,18.5 -171.5,18.5 C-181.71,18.5 -190,10.21 -190,0 C-190,0 -190,0 -190,0 C-190,-10.21 -181.71,-18.5 -171.5,-18.5 C-171.5,-18.5 171.5,-18.5 171.5,-18.5 C181.71,-18.5 190,-10.21 190,0c " />
- </group>
- </group>
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/mock_webpage_light_mode.xml b/quickstep/res/drawable/mock_webpage_light_mode.xml
deleted file mode 100644
index 98abb92..0000000
--- a/quickstep/res/drawable/mock_webpage_light_mode.xml
+++ /dev/null
@@ -1,263 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group android:name="_R_G_L_0_G">
- <group android:name="_R_G_L_0_G_L_4_G">
- <group
- android:name="_R_G_L_0_G_L_4_G_L_11_G"
- android:scaleX="0.87473"
- android:scaleY="0.98643"
- android:translateX="206"
- android:translateY="472.769">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M235.5 -407 C235.5,-407 235.5,407 235.5,407 C235.5,416.93 227.43,425 217.5,425 C217.5,425 -217.5,425 -217.5,425 C-227.43,425 -235.5,416.93 -235.5,407 C-235.5,407 -235.5,-407 -235.5,-407 C-235.5,-416.93 -227.43,-425 -217.5,-425 C-217.5,-425 217.5,-425 217.5,-425 C227.43,-425 235.5,-416.93 235.5,-407c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_10_G"
- android:translateX="182.5"
- android:translateY="831">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_9_G"
- android:translateX="186"
- android:translateY="801">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -3 C162,-3 162,3 162,3 C162,7.42 158.42,11 154,11 C154,11 -154,11 -154,11 C-158.42,11 -162,7.42 -162,3 C-162,3 -162,-3 -162,-3 C-162,-7.42 -158.42,-11 -154,-11 C-154,-11 154,-11 154,-11 C158.42,-11 162,-7.42 162,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_8_G"
- android:translateX="119"
- android:translateY="755">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M95 -3 C95,-3 95,3 95,3 C95,7.42 91.42,11 87,11 C87,11 -87,11 -87,11 C-91.42,11 -95,7.42 -95,3 C-95,3 -95,-3 -95,-3 C-95,-7.42 -91.42,-11 -87,-11 C-87,-11 87,-11 87,-11 C91.42,-11 95,-7.42 95,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_7_G"
- android:translateX="182.5"
- android:translateY="725">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M158.5 -3 C158.5,-3 158.5,3 158.5,3 C158.5,7.42 154.92,11 150.5,11 C150.5,11 -150.5,11 -150.5,11 C-154.92,11 -158.5,7.42 -158.5,3 C-158.5,3 -158.5,-3 -158.5,-3 C-158.5,-7.42 -154.92,-11 -150.5,-11 C-150.5,-11 150.5,-11 150.5,-11 C154.92,-11 158.5,-7.42 158.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_6_G"
- android:translateX="197.5"
- android:translateY="695">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M173.5 -3 C173.5,-3 173.5,3 173.5,3 C173.5,7.42 169.92,11 165.5,11 C165.5,11 -165.5,11 -165.5,11 C-169.92,11 -173.5,7.42 -173.5,3 C-173.5,3 -173.5,-3 -173.5,-3 C-173.5,-7.42 -169.92,-11 -165.5,-11 C-165.5,-11 165.5,-11 165.5,-11 C169.92,-11 173.5,-7.42 173.5,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_5_G"
- android:translateX="192"
- android:translateY="665">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M168 -3 C168,-3 168,3 168,3 C168,7.42 164.42,11 160,11 C160,11 -160,11 -160,11 C-164.42,11 -168,7.42 -168,3 C-168,3 -168,-3 -168,-3 C-168,-7.42 -164.42,-11 -160,-11 C-160,-11 160,-11 160,-11 C164.42,-11 168,-7.42 168,-3c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_4_G"
- android:translateX="105.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_3_G"
- android:translateX="47.5"
- android:translateY="360">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M23.5 -2 C23.5,-2 23.5,2 23.5,2 C23.5,4.21 21.71,6 19.5,6 C19.5,6 -19.5,6 -19.5,6 C-21.71,6 -23.5,4.21 -23.5,2 C-23.5,2 -23.5,-2 -23.5,-2 C-23.5,-4.21 -21.71,-6 -19.5,-6 C-19.5,-6 19.5,-6 19.5,-6 C21.71,-6 23.5,-4.21 23.5,-2c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_2_G"
- android:translateX="142.5"
- android:translateY="328">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M118.5 -10 C118.5,-10 118.5,10 118.5,10 C118.5,14.42 114.92,18 110.5,18 C110.5,18 -110.5,18 -110.5,18 C-114.92,18 -118.5,14.42 -118.5,10 C-118.5,10 -118.5,-10 -118.5,-10 C-118.5,-14.42 -114.92,-18 -110.5,-18 C-110.5,-18 110.5,-18 110.5,-18 C114.92,-18 118.5,-14.42 118.5,-10c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_1_G"
- android:translateX="186"
- android:translateY="284">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M162 -10 C162,-10 162,10 162,10 C162,14.42 158.42,18 154,18 C154,18 -154,18 -154,18 C-158.42,18 -162,14.42 -162,10 C-162,10 -162,-10 -162,-10 C-162,-14.42 -158.42,-18 -154,-18 C-154,-18 154,-18 154,-18 C158.42,-18 162,-14.42 162,-10c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_4_G_L_0_G"
- android:translateX="155"
- android:translateY="240">
- <path
- android:name="_R_G_L_0_G_L_4_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M131 -10 C131,-10 131,10 131,10 C131,14.42 127.42,18 123,18 C123,18 -123,18 -123,18 C-127.42,18 -131,14.42 -131,10 C-131,10 -131,-10 -131,-10 C-131,-14.42 -127.42,-18 -123,-18 C-123,-18 123,-18 123,-18 C127.42,-18 131,-14.42 131,-10c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G_L_3_G"
- android:translateX="24"
- android:translateY="390">
- <group
- android:name="_R_G_L_0_G_L_3_G_L_0_G"
- android:translateX="182"
- android:translateY="120">
- <path
- android:name="_R_G_L_0_G_L_3_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M182 -98 C182,-98 182,98 182,98 C182,110.14 172.14,120 160,120 C160,120 -160,120 -160,120 C-172.14,120 -182,110.14 -182,98 C-182,98 -182,-98 -182,-98 C-182,-110.14 -172.14,-120 -160,-120 C-160,-120 160,-120 160,-120 C172.14,-120 182,-110.14 182,-98c " />
- </group>
- </group>
- <group android:name="_R_G_L_0_G_L_2_G">
- <group
- android:name="_R_G_L_0_G_L_2_G_L_2_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_0_G_L_2_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -95.63 C206,-95.63 206,42.37 206,42.37 C206,43.47 205.1,44.37 204,44.37 C204,44.37 -204,44.37 -204,44.37 C-205.1,44.37 -206,43.47 -206,42.37 C-206,42.37 -206,-95.63 -206,-95.63 C-206,-96.73 -205.1,-97.63 -204,-97.63 C-204,-97.63 204,-97.63 204,-97.63 C205.1,-97.63 206,-96.73 206,-95.63c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_2_G_L_1_G"
- android:translateX="206"
- android:translateY="145">
- <path
- android:name="_R_G_L_0_G_L_2_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M109 -14 C109,-14 109,14 109,14 C109,15.1 108.1,16 107,16 C107,16 -107,16 -107,16 C-108.1,16 -109,15.1 -109,14 C-109,14 -109,-14 -109,-14 C-109,-15.1 -108.1,-16 -107,-16 C-107,-16 107,-16 107,-16 C108.1,-16 109,-15.1 109,-14c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_2_G_L_0_G"
- android:translateX="46"
- android:translateY="145">
- <path
- android:name="_R_G_L_0_G_L_2_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#80868b"
- android:fillType="nonZero"
- android:pathData=" M22 -14 C22,-14 22,14 22,14 C22,18.42 18.42,22 14,22 C14,22 -14,22 -14,22 C-18.42,22 -22,18.42 -22,14 C-22,14 -22,-14 -22,-14 C-22,-18.42 -18.42,-22 -14,-22 C-14,-22 14,-22 14,-22 C18.42,-22 22,-18.42 22,-14c " />
- </group>
- </group>
- <group android:name="_R_G_L_0_G_L_1_G">
- <group
- android:name="_R_G_L_0_G_L_1_G_L_2_G"
- android:translateX="206"
- android:translateY="51">
- <path
- android:name="_R_G_L_0_G_L_1_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#6e7175"
- android:fillType="nonZero"
- android:pathData=" M206 -0.27 C206,-0.27 206,49.73 206,49.73 C206,49.73 -206,49.73 -206,49.73 C-206,49.73 -206,-0.27 -206,-0.27 C-206,-0.27 206,-0.27 206,-0.27c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_1_G_L_1_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_0_G_L_1_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#6e7175"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_0_G_L_1_G_L_0_G"
- android:translateX="206"
- android:translateY="66.5">
- <path
- android:name="_R_G_L_0_G_L_1_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9a9a9a"
- android:fillType="nonZero"
- android:pathData=" M190 0 C190,0 190,0 190,0 C190,10.21 181.71,18.5 171.5,18.5 C171.5,18.5 -171.5,18.5 -171.5,18.5 C-181.71,18.5 -190,10.21 -190,0 C-190,0 -190,0 -190,0 C-190,-10.21 -181.71,-18.5 -171.5,-18.5 C-171.5,-18.5 171.5,-18.5 171.5,-18.5 C181.71,-18.5 190,-10.21 190,0c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G_L_0_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bac4d6"
- android:fillType="nonZero"
- android:pathData=" M206.06 -430.06 C206.06,-430.06 206,431 206,431 C206,446 189.75,446 189.79,446 C189.79,446 -189.98,446 -189.98,446 C-189.94,446 -206,446 -206,431 C-206,431 -206,-430 -206,-430 C-206,-446 -189.97,-446 -190.01,-446 C-190.01,-446 188.98,-446.06 188.98,-446.06 C188.94,-446.06 206,-446 206.06,-430.06c " />
- </group>
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/taskbar_edu_splitscreen.png b/quickstep/res/drawable/taskbar_edu_splitscreen.png
new file mode 100644
index 0000000..f9d2a63
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_edu_splitscreen.png
Binary files differ
diff --git a/quickstep/res/drawable/taskbar_edu_stashing.png b/quickstep/res/drawable/taskbar_edu_stashing.png
new file mode 100644
index 0000000..f9d2a63
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_edu_stashing.png
Binary files differ
diff --git a/quickstep/res/drawable/taskbar_edu_switch_apps.png b/quickstep/res/drawable/taskbar_edu_switch_apps.png
new file mode 100644
index 0000000..f9d2a63
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_edu_switch_apps.png
Binary files differ
diff --git a/quickstep/res/interpolator/app_open_x.xml b/quickstep/res/interpolator/app_open_x.xml
new file mode 100644
index 0000000..5fa0bcb
--- /dev/null
+++ b/quickstep/res/interpolator/app_open_x.xml
@@ -0,0 +1,21 @@
+<?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.1217, 0.0462, 0.15, 0.4686, 0.1667, 0.66 C 0.1834, 0.8878, 0.1667, 1, 1, 1"/>
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
new file mode 100644
index 0000000..70c4231
--- /dev/null
+++ b/quickstep/res/interpolator/three_point_fast_out_extra_slow_in.xml
@@ -0,0 +1,21 @@
+<?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/gesture_tutorial_dialog.xml b/quickstep/res/layout/gesture_tutorial_dialog.xml
index 59bf7b9..db6ec85 100644
--- a/quickstep/res/layout/gesture_tutorial_dialog.xml
+++ b/quickstep/res/layout/gesture_tutorial_dialog.xml
@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index cdda43c..cfb3eb0 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -49,7 +49,7 @@
android:scaleY="0.98"
android:visibility="invisible" />
- <View
+ <FrameLayout
android:id="@+id/gesture_tutorial_fake_task_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -62,7 +62,7 @@
android:background="@drawable/gesture_tutorial_ripple"/>
<ImageView
- android:id="@+id/gesture_tutorial_feedback_video"
+ android:id="@+id/gesture_tutorial_edge_gesture_video"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
@@ -73,14 +73,12 @@
android:visibility="gone"/>
<ImageView
- android:id="@+id/gesture_tutorial_gesture_video"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentTop="true"
- android:layout_alignParentBottom="true"
- android:layout_alignParentStart="true"
- android:layout_alignParentEnd="true"
- android:scaleType="fitXY"
+ android:id="@+id/gesture_tutorial_finger_dot"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gesture_tutorial_finger_dot"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
android:visibility="gone"/>
<androidx.constraintlayout.widget.ConstraintLayout
diff --git a/quickstep/res/layout/gesture_tutorial_mock_conversation.xml b/quickstep/res/layout/gesture_tutorial_mock_conversation.xml
new file mode 100644
index 0000000..89973d3
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_mock_conversation.xml
@@ -0,0 +1,242 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<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">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="101dp"
+ android:background="@color/mock_conversation_top_bar"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="43dp"
+ android:layout_marginBottom="22dp"
+ android:layout_marginStart="34dp"
+ android:layout_marginEnd="211dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_conversation_top_bar_item"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="43dp"
+ android:layout_marginBottom="22dp"
+ android:layout_marginStart="300dp"
+ android:layout_marginEnd="16dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_conversation_top_bar_item"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/top_bar_button"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/top_bar_button"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="43dp"
+ android:layout_marginBottom="22dp"
+ android:layout_marginStart="300dp"
+ android:layout_marginEnd="24dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_conversation_top_bar_item"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:background="@color/mock_conversation_background"
+ android:paddingBottom="66dp"
+
+ app:layout_constraintTop_toBottomOf="@id/top_bar"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:paddingBottom="36dp"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/message_bar"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/message_1"
+ android:layout_width="0dp"
+ android:layout_height="112dp"
+ android:layout_marginBottom="32dp"
+ android:layout_marginStart="124dp"
+ android:layout_marginEnd="18dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="18dp"
+ app:cardBackgroundColor="@color/mock_conversation_sent_message"
+ app:layout_constraintBottom_toTopOf="@id/reply_icon_1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/reply_icon_1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginBottom="32dp"
+ android:layout_marginStart="26dp"
+ android:layout_marginEnd="342dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_conversation_profile_icon"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintBottom_toTopOf="@id/message_2"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="36dp"
+ android:layout_marginStart="17dp"
+ android:layout_marginEnd="112dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="18dp"
+ app:cardBackgroundColor="@color/mock_conversation_received_message"
+ app:layout_constraintTop_toTopOf="@id/reply_icon_1"
+ app:layout_constraintBottom_toBottomOf="@id/reply_icon_1"
+ app:layout_constraintStart_toEndOf="@id/reply_icon_1"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/message_2"
+ android:layout_width="0dp"
+ android:layout_height="36dp"
+ android:layout_marginBottom="4dp"
+ android:layout_marginStart="280dp"
+ android:layout_marginEnd="18dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="18dp"
+ app:cardBackgroundColor="@color/mock_conversation_sent_message"
+ app:layout_constraintBottom_toTopOf="@id/message_3"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/message_3"
+ android:layout_width="0dp"
+ android:layout_height="74dp"
+ android:layout_marginBottom="32dp"
+ android:layout_marginStart="124dp"
+ android:layout_marginEnd="18dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="18dp"
+ app:cardBackgroundColor="@color/mock_conversation_sent_message"
+ app:layout_constraintBottom_toTopOf="@id/reply_icon_2"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/reply_icon_2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginBottom="32dp"
+ android:layout_marginStart="26dp"
+ android:layout_marginEnd="342dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_conversation_profile_icon"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintBottom_toTopOf="@id/message_4"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="36dp"
+ android:layout_marginStart="17dp"
+ android:layout_marginEnd="144dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="18dp"
+ app:cardBackgroundColor="@color/mock_conversation_received_message"
+ app:layout_constraintTop_toTopOf="@id/reply_icon_2"
+ app:layout_constraintBottom_toBottomOf="@id/reply_icon_2"
+ app:layout_constraintStart_toEndOf="@id/reply_icon_2"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/message_4"
+ android:layout_width="0dp"
+ android:layout_height="74dp"
+ android:layout_marginStart="124dp"
+ android:layout_marginEnd="18dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="18dp"
+ app:cardBackgroundColor="@color/mock_conversation_sent_message"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/message_bar"
+ android:layout_width="0dp"
+ android:layout_height="44dp"
+ android:layout_marginTop="36dp"
+ android:layout_marginStart="34dp"
+ android:layout_marginEnd="24dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="164dp"
+ app:cardBackgroundColor="@color/mock_conversation_message_input"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ 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_mock_conversation_list.xml b/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml
new file mode 100644
index 0000000..ad6b165
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml
@@ -0,0 +1,394 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<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">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="101dp"
+ android:background="@color/mock_list_top_bar"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="43dp"
+ android:layout_marginBottom="22dp"
+ android:layout_marginStart="34dp"
+ android:layout_marginEnd="35dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_top_bar_item"
+ 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:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:background="@color/mock_list_background"
+ android:paddingBottom="66dp"
+
+ app:layout_constraintTop_toBottomOf="@id/top_bar"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:paddingTop="28dp"
+ android:paddingStart="26dp"
+ android:paddingBottom="14dp"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/mock_button">
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_icon_1"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_list_profile_icon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_1"
+ android:layout_width="0dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="217dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="@id/conversation_icon_1"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/conversation_line_2"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_2"
+ android:layout_width="0dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="142dp"
+ android:layout_marginTop="4dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintTop_toBottomOf="@id/conversation_line_1"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/conversation_icon_1"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_icon_2"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_marginTop="32dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_list_profile_icon"
+ app:layout_constraintTop_toBottomOf="@id/conversation_icon_1"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_3"
+ android:layout_width="0dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="190dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="@id/conversation_icon_2"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_2"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/conversation_line_4"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_4"
+ android:layout_width="0dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="171dp"
+ android:layout_marginTop="4dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintTop_toBottomOf="@id/conversation_line_3"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_2"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/conversation_icon_2"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_icon_3"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_marginTop="32dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_list_profile_icon"
+ app:layout_constraintTop_toBottomOf="@id/conversation_icon_2"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_5"
+ android:layout_width="0dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="198dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="@id/conversation_icon_3"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_3"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/conversation_line_6"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_6"
+ android:layout_width="0dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="79dp"
+ android:layout_marginTop="4dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintTop_toBottomOf="@id/conversation_line_5"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_3"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/conversation_icon_3"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_icon_4"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_marginTop="32dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_list_profile_icon"
+ app:layout_constraintTop_toBottomOf="@id/conversation_icon_3"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_7"
+ android:layout_width="0dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="174dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="@id/conversation_icon_4"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_4"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/conversation_line_8"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_8"
+ android:layout_width="0dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="117dp"
+ android:layout_marginTop="4dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintTop_toBottomOf="@id/conversation_line_7"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_4"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/conversation_icon_4"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_icon_5"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_marginTop="32dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_list_profile_icon"
+ app:layout_constraintTop_toBottomOf="@id/conversation_icon_4"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_9"
+ android:layout_width="0dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="244dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="@id/conversation_icon_5"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_5"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/conversation_line_10"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_10"
+ android:layout_width="0dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="143dp"
+ android:layout_marginTop="4dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintTop_toBottomOf="@id/conversation_line_9"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_5"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/conversation_icon_5"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_icon_6"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_marginTop="32dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_list_profile_icon"
+ app:layout_constraintTop_toBottomOf="@id/conversation_icon_5"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_11"
+ android:layout_width="0dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="177dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="@id/conversation_icon_6"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_6"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/conversation_line_12"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_12"
+ android:layout_width="0dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="117dp"
+ android:layout_marginTop="4dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintTop_toBottomOf="@id/conversation_line_11"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_6"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/conversation_icon_6"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_icon_7"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_marginTop="32dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_list_profile_icon"
+ app:layout_constraintTop_toBottomOf="@id/conversation_icon_6"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_13"
+ android:layout_width="0dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="189dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="@id/conversation_icon_7"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_7"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/conversation_line_14"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/conversation_line_14"
+ android:layout_width="0dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="166dp"
+ android:layout_marginTop="4dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_list_preview_message"
+ app:layout_constraintTop_toBottomOf="@id/conversation_line_13"
+ app:layout_constraintStart_toEndOf="@id/conversation_icon_7"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/conversation_icon_7"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_button"
+ android:layout_width="149dp"
+ android:layout_height="56dp"
+ android:layout_marginEnd="24dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="164dp"
+ app:cardBackgroundColor="@color/mock_list_button"
+ app:layout_constraintBottom_toBottomOf="parent"
+ 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_mock_webpage.xml b/quickstep/res/layout/gesture_tutorial_mock_webpage.xml
new file mode 100644
index 0000000..ab00a11
--- /dev/null
+++ b/quickstep/res/layout/gesture_tutorial_mock_webpage.xml
@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<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">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/url_bar"
+ android:layout_width="match_parent"
+ android:layout_height="101dp"
+ android:background="@color/mock_webpage_url_bar"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="48dp"
+ android:layout_marginBottom="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="100dp"
+ app:cardBackgroundColor="@color/mock_webpage_url_bar_item"
+ 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/top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="88dp"
+ android:background="@color/mock_webpage_top_bar"
+
+ app:layout_constraintTop_toBottomOf="@id/url_bar"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/top_bar_button"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="22dp"
+ android:layout_marginBottom="22dp"
+ android:layout_marginStart="24dp"
+ android:layout_marginEnd="344dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="@color/mock_webpage_top_bar_item"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginBottom="28dp"
+ android:layout_marginStart="97dp"
+ android:layout_marginEnd="97dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="2dp"
+ app:cardBackgroundColor="@color/mock_webpage_top_bar_item"
+ 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:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:background="@color/mock_webpage_background"
+ android:paddingTop="32dp"
+ android:paddingStart="24dp"
+ android:paddingBottom="50dp"
+
+ app:layout_constraintTop_toBottomOf="@id/top_bar"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_1"
+ android:layout_width="0dp"
+ android:layout_height="36dp"
+ android:layout_marginEnd="126dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_2"
+ android:layout_width="0dp"
+ android:layout_height="36dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="64dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_3"
+ android:layout_width="0dp"
+ android:layout_height="36dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="151dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_2"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_button"
+ android:layout_width="47dp"
+ android:layout_height="12dp"
+ android:layout_marginTop="8dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_3"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="47dp"
+ android:layout_height="12dp"
+ android:background="@color/mock_webpage_page_text"
+ android:layout_marginStart="11dp"
+ android:layout_marginTop="8dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="4dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_3"
+ app:layout_constraintStart_toEndOf="@id/mock_button"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_block"
+ android:layout_width="0dp"
+ android:layout_height="240dp"
+ android:layout_marginTop="24dp"
+ android:layout_marginEnd="24dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="22dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_4"
+ android:layout_width="0dp"
+ android:layout_height="22dp"
+ android:layout_marginTop="24dp"
+ android:layout_marginEnd="52dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_block"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_5"
+ android:layout_width="0dp"
+ android:layout_height="22dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="41dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_4"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_6"
+ android:layout_width="0dp"
+ android:layout_height="22dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="71dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_5"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_7"
+ android:layout_width="0dp"
+ android:layout_height="22dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="198dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_6"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/mock_line_8"
+ android:layout_width="0dp"
+ android:layout_height="22dp"
+ android:layout_marginTop="24dp"
+ android:layout_marginEnd="64dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_7"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="0dp"
+ android:layout_height="22dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="71dp"
+
+ app:cardElevation="0dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="@color/mock_webpage_page_text"
+ app:layout_constraintTop_toBottomOf="@id/mock_line_8"
+ app:layout_constraintStart_toStartOf="parent"
+ 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/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 0c2a28c..acbb5b9 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -47,6 +47,23 @@
android:layout_weight="1" />
<Button
+ android:id="@+id/action_split"
+ style="@style/OverviewActionButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableStart="@drawable/ic_split_screen"
+ android:text="@string/action_split"
+ android:theme="@style/ThemeControlHighlightWorkspaceColor"
+ android:visibility="gone" />
+
+ <Space
+ android:id="@+id/action_split_space"
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1"
+ android:visibility="gone" />
+
+ <Button
android:id="@+id/action_share"
style="@style/OverviewActionButton"
android:layout_width="wrap_content"
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 1ee726e..1dea57e 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -16,7 +16,7 @@
-->
<com.android.quickstep.views.ClearAllButton
xmlns:android="http://schemas.android.com/apk/res/android"
- style="@android:style/Widget.DeviceDefault.Button.Borderless"
+ style="@style/OverviewClearAllButton"
android:id="@+id/clear_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
new file mode 100644
index 0000000..069ff86
--- /dev/null
+++ b/quickstep/res/layout/task_grouped.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- NOTE! don't add dimensions for margins / paddings / sizes that change per orientation to this
+ file, they need to be loaded at runtime. -->
+
+<!-- DOUBLE NOTE! Don't deviate IDs from task.xml since this layout acts as a "subclass" (read as
+ "bad code"). How can we use the view pool in RecentsView to use task.xml layout with using
+ GroupedTaskView.java class? Is that possible (while still keeping code in separate class) ? -->
+
+<com.android.quickstep.views.GroupedTaskView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:defaultFocusHighlightEnabled="false"
+ android:focusable="true">
+
+ <com.android.quickstep.views.TaskThumbnailView
+ android:id="@+id/snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <com.android.quickstep.views.TaskThumbnailView
+ android:id="@+id/bottomright_snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <com.android.quickstep.views.IconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/task_thumbnail_icon_size"
+ android:layout_height="@dimen/task_thumbnail_icon_size"
+ android:focusable="false"
+ android:importantForAccessibility="no"/>
+</com.android.quickstep.views.GroupedTaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index c0e0862..83ad9f3 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -52,6 +52,7 @@
android:orientation="horizontal"
android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+ android:layout_marginEnd="@dimen/taskbar_contextual_button_margin"
android:gravity="center_vertical"
android:layout_gravity="end"/>
@@ -65,13 +66,12 @@
android:layout_gravity="end"/>
</FrameLayout>
- <View
+ <com.android.launcher3.taskbar.StashedHandleView
android:id="@+id/stashed_handle"
tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- tools:comment2="TODO: Tint dynamically"
- android:background="?android:attr/textColorPrimary"
+ android:background="@color/taskbar_stashed_handle_dark_color"
android:clipToOutline="true"
android:layout_gravity="bottom"/>
diff --git a/quickstep/res/layout/taskbar_contextual_button.xml b/quickstep/res/layout/taskbar_contextual_button.xml
new file mode 100644
index 0000000..cbbbfab
--- /dev/null
+++ b/quickstep/res/layout/taskbar_contextual_button.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/taskbar_contextual_buttons_size"
+ android:layout_height="@dimen/taskbar_contextual_buttons_size"
+ android:layout_marginTop="@dimen/taskbar_contextual_button_margin"
+ android:paddingStart="@dimen/taskbar_nav_buttons_spacing"
+ android:background="@drawable/taskbar_icon_click_feedback_roundrect"
+ android:scaleType="center"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_edu.xml b/quickstep/res/layout/taskbar_edu.xml
new file mode 100644
index 0000000..3796ff9
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.taskbar.TaskbarEduView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:gravity="bottom"
+ android:orientation="vertical"
+ android:layout_marginHorizontal="108dp">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/edu_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/bg_rounded_corner_bottom_sheet"
+ android:gravity="center_horizontal"
+ android:paddingHorizontal="36dp"
+ android:paddingTop="64dp">
+
+ <com.android.launcher3.taskbar.TaskbarEduPagedView
+ android:id="@+id/content"
+ android:clipToPadding="false"
+ android:layout_width="match_parent"
+ android:layout_height="378dp"
+ app:layout_constraintTop_toTopOf="parent"
+ launcher:pageIndicator="@+id/content_page_indicator">
+
+ <LinearLayout
+ android:id="@+id/page_switch_apps"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.TaskbarEdu.Title"
+ android:text="@string/taskbar_edu_switch_apps"/>
+
+ <ImageView
+ android:layout_width="322dp"
+ android:layout_height="282dp"
+ android:layout_marginTop="16dp"
+ android:src="@drawable/taskbar_edu_switch_apps"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/page_splitscreen"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.TaskbarEdu.Title"
+ android:text="@string/taskbar_edu_splitscreen"/>
+
+ <ImageView
+ android:layout_width="322dp"
+ android:layout_height="282dp"
+ android:layout_marginTop="16dp"
+ android:src="@drawable/taskbar_edu_splitscreen"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/page_stashing"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.TaskbarEdu.Title"
+ android:text="@string/taskbar_edu_stashing"/>
+
+ <ImageView
+ android:layout_width="322dp"
+ android:layout_height="282dp"
+ android:layout_marginTop="16dp"
+ android:src="@drawable/taskbar_edu_stashing"/>
+ </LinearLayout>
+ </com.android.launcher3.taskbar.TaskbarEduPagedView>
+
+ <Button
+ android:id="@+id/edu_start_button"
+ android:layout_width="wrap_content"
+ android:layout_height="36dp"
+ android:layout_marginBottom="92dp"
+ android:layout_marginTop="32dp"
+ app:layout_constraintTop_toBottomOf="@id/content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:text="@string/taskbar_edu_close"
+ style="@style/TaskbarEdu.Button.Close"
+ android:textColor="?android:attr/textColorPrimary"/>
+
+ <com.android.launcher3.pageindicators.PageIndicatorDots
+ android:id="@+id/content_page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="@id/edu_start_button"
+ app:layout_constraintBottom_toBottomOf="@id/edu_start_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:elevation="1dp" />
+
+ <Button
+ android:id="@+id/edu_end_button"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="@id/edu_start_button"
+ app:layout_constraintBottom_toBottomOf="@id/edu_start_button"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:text="@string/taskbar_edu_next"
+ style="@style/TaskbarEdu.Button.Next"
+ android:textColor="?androidprv:attr/textColorOnAccent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.launcher3.taskbar.TaskbarEduView>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_nav_button.xml b/quickstep/res/layout/taskbar_nav_button.xml
index 985f928..4ffb8d8 100644
--- a/quickstep/res/layout/taskbar_nav_button.xml
+++ b/quickstep/res/layout/taskbar_nav_button.xml
@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/taskbar_nav_buttons_size"
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 433ef86..0af80f9 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Stelselnavigasie-instellings"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Deel"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skermkiekie"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Jy kan dit later in die <xliff:g id="NAME">%1$s</xliff:g>-program kry"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Kanselleer"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Slaan oor"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Taakbalkopvoeding het verskyn"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Taakbalkopvoeding is toegemaak"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Volgende"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Terug"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Maak toe"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Klaar"</string>
</resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 838dd60..7786191 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"የስርዓት አሰሳ ቅንብሮች"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"አጋራ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ቅጽበታዊ ገጽ እይታ"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ይህ ድርጊት በመተግበሪያው ወይም በእርስዎ ድርጅት አይፈቀድም"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"የአሰሳ አጋዥ ሥልጠናን ይዝለሉ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"ይህን በኋላ በ<xliff:g id="NAME">%1$s</xliff:g> መተግበሪያው ውስጥ ማግኘት ይችላሉ"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ይቅር"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ዝለል"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"የተግባር አሞሌ ትምህርት ይታያል"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"የተግባር አሞሌ ትምህርት ተዘግቷል"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ቀጣይ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ተመለስ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"ዝጋ"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"ተጠናቅቋል"</string>
</resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 3f26977..25b66d5 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"إعدادات التنقّل داخل النظام"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"مشاركة"</string>
<string name="action_screenshot" msgid="8171125848358142917">"لقطة شاشة"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"لا يسمح التطبيق أو لا تسمح مؤسستك بهذا الإجراء."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"هل تريد تخطي الدليل التوجيهي؟"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"يمكنك العثور على هذا الدليل التوجيهي لاحقًا في التطبيق \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"إلغاء"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"التخطي"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ظهرت لوحة تعليم استخدام شريط المهام."</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"تم إغلاق لوحة تعليم استخدام شريط المهام."</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"الشاشة التالية"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"رجوع"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"إغلاق"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"تم"</string>
</resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 2551fc4..5379925 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ছিষ্টেম নেভিগেশ্বনৰ ছেটিং"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"শ্বেয়াৰ কৰক"</string>
<string name="action_screenshot" msgid="8171125848358142917">"স্ক্ৰীনশ্বট"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"এপ্টোৱে অথবা আপোনাৰ প্ৰতিষ্ঠানে এই কাৰ্যটোৰ অনুমতি নিদিয়ে"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"নেভিগেশ্বনৰ টিউট’ৰিয়েল এৰিব বিচাৰে নেকি?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"আপুনি এয়া পাছত <xliff:g id="NAME">%1$s</xliff:g> এপ্টোত বিচাৰিব পাৰিব"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"বাতিল কৰক"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"এৰি যাওক"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"টাস্কবাৰৰ শিক্ষাৰ পেনেলটো প্ৰদর্শিত হৈছে"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"টাস্কবাৰৰ শিক্ষাৰ পেনেলটো বন্ধ হৈছে"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"পৰৱৰ্তী"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"উভতি যাওক"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"বন্ধ কৰক"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"হ’ল"</string>
</resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index d01bf3f..5498817 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistem naviqasiya ayarları"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Paylaşın"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skrinşot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Bunu sonra <xliff:g id="NAME">%1$s</xliff:g> tətbiqində tapa bilərsiniz"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Ləğv edin"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ötürün"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Tapşırıq panelindəki təlim bölməsi görünür"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Tapşırıq panelindəki təlim bölməsi bağlanıb"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Sonra"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Geri"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Bağlayın"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Hazırdır"</string>
</resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index bde409f..81d0a08 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Podešavanja kretanja kroz sistem"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Deli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimak ekrana"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Možete da pronađete ovo kasnije u aplikaciji <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Otkaži"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Preskoči"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Edukativno okno iz trake zadataka se pojavilo"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Edukativno okno iz trake zadataka je zatvoreno"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Dalje"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Nazad"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Zatvori"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Gotovo"</string>
</resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 8e57bd8..586d78d 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Налады навігацыі ў сістэме"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Абагуліць"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Здымак экрана"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Гэта дзеянне не дазволена праграмай ці вашай арганізацыяй"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Прапусціць дапаможнік па навігацыі?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Знайсці дапаможнік можна ў праграме \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Скасаваць"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Прапусціць"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"З\'явілася панэль навучання на панэлі задач"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Панэль навучання на панэлі задач закрыта"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Далей"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Назад"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Закрыць"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Гатова"</string>
</resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 7305d9d..389b085 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Настройки за навигиране в системата"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Споделяне"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Екранна снимка"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Това действие не е разрешено от приложението или организацията ви"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Пропускане на урока за навигиране?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Урокът е налице в приложението <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Отказ"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Пропускане"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Показва се урокът за лентата на задачите"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Урокът за лентата на задачите бе затворен"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Напред"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Назад"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Затваряне"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
</resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 1d0b45f..9d3ac80 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"সিস্টেম নেভিগেশন সেটিংস"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"শেয়ার করুন"</string>
<string name="action_screenshot" msgid="8171125848358142917">"স্ক্রিনশট নিন"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"এই অ্যাপ বা আপনার প্রতিষ্ঠান এই অ্যাকশনটি পারফর্ম করার অনুমতি দেয়নি"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"নেভিগেশন টিউটোরিয়াল এড়িয়ে যেতে চান?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"আপনি <xliff:g id="NAME">%1$s</xliff:g> অ্যাপে পরে এটি খুঁজে পাবেন"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"বাতিল করুন"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"এড়িয়ে যান"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"টাস্কবার এডুকেশন দেখানো হয়েছে"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"টাস্কবার এডুকেশন বন্ধ করা আছে"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"পরবর্তী"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ফিরুন"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"বন্ধ করুন"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"হয়ে গেছে"</string>
</resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index acd7b10..0b8d34a 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Postavke navigiranja sistemom"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Dijeli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimak ekrana"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"To možete pronaći kasnije u aplikaciji <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Otkaži"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Preskoči"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Edukacija o programskoj traci je prikazana"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Edukacija o programskoj traci je zatvorena"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Naprijed"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Nazad"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Zatvori"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Gotovo"</string>
</resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index a0d6aee..673e25b 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configuració de navegació del sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Comparteix"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Captura de pantalla"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Pots trobar-lo més tard a l\'aplicació <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancel·la"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Omet"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Ha aparegut el tauler educatiu de la barra de tasques"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"S\'ha tancat el tauler educatiu de la barra de tasques"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Següent"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Enrere"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Tanca"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Fet"</string>
</resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index b2d092e..50b6049 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Nastavení navigace v systému"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Sdílet"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snímek obrazovky"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Program později najdete v aplikaci <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Zrušit"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Přeskočit"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Zobrazila se výuka k hlavnímu panelu"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Výuka k hlavnímu panelu byla zavřena"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Další"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Zpět"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Zavřít"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Hotovo"</string>
</resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index f2e2df0..5e91710 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Indstillinger for systemnavigation"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Del"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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 selvstudiet for navigation over?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du kan finde dette senere i appen <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuller"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Spring over"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Vejledningen om proceslinjen blev åbnet"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Vejledningen om proceslinjen blev lukket"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Næste"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Tilbage"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Luk"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Luk"</string>
</resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 4a023e3..999a241 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Einstellungen der Systemsteuerung"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Teilen"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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">"Navigationstutorial überspringen?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du findest es später auch in der <xliff:g id="NAME">%1$s</xliff:g> App"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Abbrechen"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Überspringen"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Anleitung für Taskleiste eingeblendet"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Anleitung für Taskleiste geschlossen"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Weiter"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Zurück"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Schließen"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Fertig"</string>
</resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 7243e03..063d769 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Ρυθμίσεις πλοήγησης συστήματος"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Κοινοποίηση"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Στιγμιότυπο οθόνης"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Αυτή η ενέργεια δεν επιτρέπεται από την εφαρμογή ή τον οργανισμό σας."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Παράβλεψη οδηγού πλοήγησης;"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Βρείτε τον αργότερα στην εφαρμογή <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Ακύρωση"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Παράβλεψη"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Η εκπαίδευση για τη γραμμή εργασιών εμφανίστηκε"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Η εκπαίδευση για τη γραμμή εργασιών έκλεισε"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Επόμενο"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Πίσω"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Κλείσιμο"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Τέλος"</string>
</resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 7b42aac..78d5bcc 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"System navigation settings"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"You can find this later in the <xliff:g id="NAME">%1$s</xliff:g> app"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancel"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Skip"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Taskbar education appeared"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Taskbar education closed"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Next"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Back"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
</resources>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index 7b42aac..78d5bcc 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"System navigation settings"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"You can find this later in the <xliff:g id="NAME">%1$s</xliff:g> app"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancel"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Skip"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Taskbar education appeared"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Taskbar education closed"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Next"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Back"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
</resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 7b42aac..78d5bcc 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"System navigation settings"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"You can find this later in the <xliff:g id="NAME">%1$s</xliff:g> app"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancel"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Skip"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Taskbar education appeared"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Taskbar education closed"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Next"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Back"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
</resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 7b42aac..78d5bcc 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"System navigation settings"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"You can find this later in the <xliff:g id="NAME">%1$s</xliff:g> app"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancel"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Skip"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Taskbar education appeared"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Taskbar education closed"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Next"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Back"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
</resources>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
index 35ec6bb..c0ce050 100644
--- a/quickstep/res/values-en-rXC/strings.xml
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -79,9 +79,17 @@
<string name="allset_navigation_settings" msgid="4713404605961476027">""<annotation id="link">"System navigation settings"</annotation>""</string>
<string name="action_share" msgid="2648470652637092375">"Share"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <string name="action_split" msgid="2098009717623550676">"Split"</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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"You can find this later in the <xliff:g id="NAME">%1$s</xliff:g> app"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancel"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Skip"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Taskbar education appeared"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Taskbar education closed"</string>
+ <string name="taskbar_edu_stashing" msgid="5212374387411764031">"Touch & hold to hide the taskbar"</string>
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Next"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Back"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Close"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Done"</string>
</resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index e61fd91..08a5f6b 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configuración de navegación del sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Captura de pantalla"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Puedes encontrarlo en la app de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancelar"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Omitir"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Se abrió la barra de herramientas Educación"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Se cerró la barra de herramientas Educación"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Siguiente"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Atrás"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Cerrar"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Listo"</string>
</resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 8e8829e..3c2a726 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Ajustes de navegación del sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Hacer captura"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Puedes consultarlo en otro momento en la aplicación <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancelar"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Saltar"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Ha aparecido una nota sobre la barra de tareas"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Nota sobre la barra de tareas cerrada"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Siguiente"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Atrás"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Cerrar"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Hecho"</string>
</resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 74b7528..53d6660 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Süsteemi navigeerimisseaded"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Jaga"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Ekraanipilt"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Leiate selle hiljem rakendusest <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Tühista"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Jäta vahele"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Tegumiriba juhised kuvati"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Tegumiriba juhised on suletud"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Järgmine"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Tagasi"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Sule"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Valmis"</string>
</resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index bd016e9..1627648 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sisteman nabigatzeko ezarpenak"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Partekatu"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Atera pantaila-argazki bat"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"<xliff:g id="NAME">%1$s</xliff:g> aplikazioan dago eskuragarri tutoriala"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Utzi"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Saltatu"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Agertu egin da zereginen barraren tutoriala"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Itxi egin da zereginen barraren tutoriala"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Hurrengoa"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Atzera"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Itxi"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Eginda"</string>
</resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index ae260ed..a2be4fe 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"تنظیمات پیمایش سیستم"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"همرسانی"</string>
<string name="action_screenshot" msgid="8171125848358142917">"نماگرفت"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"برنامه یا سازمان شما اجازه نمیدهد این کنش انجام شود."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"آموزش گامبهگام پیمایش رد شود؟"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"میتوانید آن را بعداً در برنامه <xliff:g id="NAME">%1$s</xliff:g> پیدا کنید"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"لغو"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"رد شدن"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"پانل آموزشی نوار وظیفه نمایان شد"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"پانل آموزشی نوار وظیفه بسته شد"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"بعدی"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"برگشت"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"بستن"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"تمام"</string>
</resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 8762feb..a158427 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Järjestelmän navigointiasetukset"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Jaa"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Kuvakaappaus"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Löydät tämän myöhemmin sovelluksesta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Peru"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ohita"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Tehtäväpalkin ohje näkyvissä"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Tehtäväpalkin ohje suljettu"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Seuraava"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Takaisin"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Sulje"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Valmis"</string>
</resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index a3c9393..be10674 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Paramètres de navigation du système"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Partager"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Capture d\'écran"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous trouverez le tutoriel dans l\'application <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuler"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ignorer"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"La barre des tâches éducatives s\'est affichée"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"La barre des tâches éducatives est fermée"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Suivant"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Retour"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Fermer"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"OK"</string>
</resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 5164792..8a2bdcb 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Paramètres de navigation système"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Partager"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Capture d\'écran"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous le retrouverez dans l\'appli <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuler"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Passer"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Infos sur la barre des tâches affichées"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Infos sur la barre des tâches fermées"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Suivant"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Retour"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Fermer"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"OK"</string>
</resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index baa87e7..87d0967 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configuración da navegación do sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Facer captura"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Podes atopar isto máis tarde na aplicación <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancelar"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Omitir"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Panel de información de barra de tarefas aberto"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Panel de información de barra de tarefas pechado"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Seguinte"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Atrás"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Pechar"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Feito"</string>
</resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 33e8617..8e10f84 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"સિસ્ટમના નૅવિગેશન સેટિંગ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"શેર કરો"</string>
<string name="action_screenshot" msgid="8171125848358142917">"સ્ક્રીનશૉટ"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ઍપ કે તમારી સંસ્થા દ્વારા આ ક્રિયા કરવાની મંજૂરી નથી"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"નૅવિગેશન ટ્યૂટૉરિઅલ છોડી દઈએ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"તમે આને પછીથી <xliff:g id="NAME">%1$s</xliff:g> ઍપમાં જોઈ શકો છો"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"રદ કરો"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"છોડો"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ટાસ્કબારનું શિક્ષણ આપતી પૅનલ દેખાય છે"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ટાસ્કબારનું શિક્ષણ આપતી પૅનલ બંધ થઈ છે"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"આગળ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"પાછળ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"બંધ કરો"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"થઈ ગયું"</string>
</resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 50ebb27..b7c2a13 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"सिस्टम नेविगेशन सेटिंग"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"शेयर करें"</string>
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रीनशॉट लें"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ऐप्लिकेशन या आपका संगठन इस कार्रवाई की अनुमति नहीं देता"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"नेविगेशन ट्यूटोरियल छोड़ना चाहते हैं?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"आप बाद में <xliff:g id="NAME">%1$s</xliff:g> ऐप्लिकेशन पर इसे देख सकते हैं"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"अभी नहीं"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"अभी नहीं"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"टास्कबार ट्यूटोरियल दिखाया गया"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"टास्कबार ट्यूटोरियल बंद किया गया"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"आगे बढ़ें"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"वापस जाएं"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"बंद करें"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"हो गया"</string>
</resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 50efb74..238cb15 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Postavke navigacije sustavom"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Podijeli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimka zaslona"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Kasnije ga možete pronaći u aplikaciji <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Odustani"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Preskoči"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Upute za programsku traku su se pojavile"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Upute za programsku traku su zatvorene"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Dalje"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Natrag"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Zatvori"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Gotovo"</string>
</resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index e9bd2bf..cebf4bd 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Rendszer-navigációs beállítások"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Megosztás"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Képernyőkép"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Ezt később megtalálhatja a(z) <xliff:g id="NAME">%1$s</xliff:g> alkalmazásban"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Mégse"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Kihagyás"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Az eszköztár használatát ismertető panel megjelent"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Az eszköztár használatát ismertető panel bezárult"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Tovább"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Vissza"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Bezárás"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Kész"</string>
</resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index e2c0af3..36ebd65 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Նավիգացիայի համակարգային կարգավորումներ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Կիսվել"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Սքրինշոթ անել"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Այս գործողությունն արգելված է հավելվածի կամ ձեր կազմակերպության կողմից"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Բաց թողնե՞լ նավիգացիայի ուղեցույցը"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Հետագայում սա կարող եք գտնել «<xliff:g id="NAME">%1$s</xliff:g>» հավելվածում"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Չեղարկել"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Բաց թողնել"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Խնդրագոտու «Կրթություն» վահանակը բացվեց"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Խնդրագոտու «Կրթություն» վահանակը փակվեց"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Առաջ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Հետ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Փակել"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Պատրաստ է"</string>
</resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 3e24fcb..463f686 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Setelan navigasi sistem"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Bagikan"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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 navigasi?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Anda dapat menemukan tutorial ini di lain waktu di aplikasi <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Batal"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Lewati"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Edukasi taskbar ditampilkan"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Edukasi taskbar ditutup"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Berikutnya"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Kembali"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Tutup"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Selesai"</string>
</resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index eeebc71..195fcf0 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Stillingar kerfisstjórnunar"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Deila"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skjámynd"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Þú getur fundið þetta síðar í forritinu <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Hætta við"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Sleppa"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Leiðsögn verkefnastiku sýnileg"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Leiðsögn verkefnastiku lokað"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Áfram"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Til baka"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Loka"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Lokið"</string>
</resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 5a33a38..233f593 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Impostazioni Navigazione del sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Condividi"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Puoi trovarlo in un secondo momento nell\'app <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annulla"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Salta"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Riquadro Formazione barra delle applicazioni visualizzato"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Riquadro Formazione barra delle applicazioni chiuso"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Avanti"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Indietro"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Chiudi"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Fine"</string>
</resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 2677690..a29ba7d 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"הגדרות הניווט של המערכת"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"שיתוף"</string>
<string name="action_screenshot" msgid="8171125848358142917">"צילום מסך"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"האפליקציה או הארגון שלך אינם מתירים את הפעולה הזאת"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"לדלג על המדריך לניווט?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"ניתן למצוא את המדריך מאוחר יותר באפליקציה <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ביטול"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"דילוג"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"חלונית ההסברים על שורת המשימות מופיעה"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"חלונית ההסברים על שורת המשימות נסגרה"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"הבא"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"חזרה"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"סגירה"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"סיום"</string>
</resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 9c2adef..e09b729 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"システム ナビゲーションの設定"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"共有"</string>
<string name="action_screenshot" msgid="8171125848358142917">"スクリーンショット"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"この操作はアプリまたは組織で許可されていません"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"操作チュートリアルをスキップしますか?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"チュートリアルは後から <xliff:g id="NAME">%1$s</xliff:g> アプリで確認できます"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"キャンセル"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"スキップ"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"タスクバーの説明を開きました"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"タスクバーの説明を閉じました"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"次へ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"戻る"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"閉じる"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"完了"</string>
</resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index ab3715a..ac0498c 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -58,7 +58,7 @@
<string name="home_gesture_feedback_wrong_swipe_direction" msgid="6993979358080825438">"გადაფურცლეთ ზემოთ."</string>
<string name="home_gesture_feedback_complete_with_follow_up" msgid="1427872029729605034">"თქვენ შეასრულეთ მთავარ ეკრანზე დაბრუნების ჟესტი. ახლა კი შევიტყოთ, თუ როგორ დავბრუნდეთ უკან."</string>
<string name="home_gesture_feedback_complete_without_follow_up" msgid="8049099486868933882">"თქვენ შეასრულეთ მთავარ ეკრანზე დაბრუნების ჟესტი."</string>
- <string name="home_gesture_intro_title" msgid="836590312858441830">"მთავარ გვერდზე გადასასვლელად გადაფურცლეთ"</string>
+ <string name="home_gesture_intro_title" msgid="836590312858441830">"მთავარი გვერდის სანახავად გადაფურცლეთ"</string>
<string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ. ამ ჟესტს ყოველთვის მთავარი გვერდის ეკრანზე გადაყავხართ."</string>
<string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="3032757898111577225">"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ."</string>
<string name="overview_gesture_feedback_home_detected" msgid="1411130969354020489">"უფრო ხანგრძლივად დააჭირეთ თითი ფანჯარას, რომ არ დაიხუროს."</string>
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"სისტემის ნავიგაციის პარამეტრები"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"გაზიარება"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ეკრანის ანაბეჭდი"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ეს მოქმედება არ არის დაშვებული აპის ან თქვენი ორგანიზაციის მიერ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"გსურთ, გამოტოვოთ ნავიგაციის სახელმძღვანელო?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"ამის მოგვიანებით პოვნა <xliff:g id="NAME">%1$s</xliff:g> აპში შეგიძლიათ"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"გაუქმება"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"გამოტოვება"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ამოცანების ზოლის სასწავლო არე გამოჩნდა"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ამოცანების ზოლის სასწავლო არე დაიხურა"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"შემდეგი"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"უკან"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"დახურვა"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"მზადაა"</string>
</resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index d762bd8..11fad68 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Навигацияның жүйелік параметрлері"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Бөлісу"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Бұл әрекетке қолданба не ұйым рұқсат етпейді."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Қимылдар оқулығын өткізіп жіберу керек пе?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Мұны кейін <xliff:g id="NAME">%1$s</xliff:g> қолданбасынан таба аласыз."</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Бас тарту"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Өткізіп жіберу"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Тапсырмалар тақтасы бойынша нұсқаулық ашылды."</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Тапсырмалар тақтасы бойынша нұсқаулық жабылды."</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Келесі"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Артқа"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Жабу"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Дайын"</string>
</resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 2700b83..aecce96 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ការកំណត់ការរុករកប្រព័ន្ធ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"ចែករំលែក"</string>
<string name="action_screenshot" msgid="8171125848358142917">"រូបថតអេក្រង់"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"សកម្មភាពនេះមិនត្រូវបានអនុញ្ញាតដោយកម្មវិធី ឬស្ថាប័នរបស់អ្នកទេ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"រំលងមេរៀនអំពីការរុករកឬ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"អ្នកអាចស្វែងរកមេរៀននេះនៅពេលក្រោយក្នុងកម្មវិធី <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"បោះបង់"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"រំលង"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ការបង្រៀនអំពីរបារកិច្ចការបានបង្ហាញ"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ការបង្រៀនអំពីរបារកិច្ចការត្រូវបានបិទ"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"បន្ទាប់"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ថយក្រោយ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"បិទ"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"រួចរាល់"</string>
</resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 9b981c2..f5aa9d3 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ಸಿಸ್ಟಂ ನ್ಯಾವಿಗೇಶನ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"ಹಂಚಿಕೊಳ್ಳಿ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ಸ್ಕ್ರೀನ್ಶಾಟ್"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ಆ್ಯಪ್ ಅಥವಾ ನಿಮ್ಮ ಸಂಸ್ಥೆಯು ಈ ಕ್ರಿಯೆಯನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ನ್ಯಾವಿಗೇಷನ್ ಟ್ಯುಟೋರಿಯಲ್ ಸ್ಕಿಪ್ ಮಾಡಿ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ನಲ್ಲಿ ಇದನ್ನು ನಂತರ ಕಾಣಬಹುದು"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ರದ್ದುಮಾಡಿ"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ಸ್ಕಿಪ್ ಮಾಡಿ"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ಟಾಸ್ಕ್ಬಾರ್ ಶಿಕ್ಷಣ ಕಾಣಿಸಿಕೊಂಡಿದೆ"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ಟಾಸ್ಕ್ಬಾರ್ ಶಿಕ್ಷಣ ಮುಚ್ಚಿದೆ"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ಮುಂದೆ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ಹಿಂದೆ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"ಮುಚ್ಚಿರಿ"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"ಮುಗಿದಿದೆ"</string>
</resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index ba564a7..aa76e8a 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"시스템 탐색 설정"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"공유"</string>
<string name="action_screenshot" msgid="8171125848358142917">"스크린샷"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"이 작업은 앱 또는 조직에서 허용되지 않습니다."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"이동 방법 튜토리얼을 건너뛰시겠습니까?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"이 튜토리얼은 <xliff:g id="NAME">%1$s</xliff:g> 앱에서 다시 볼 수 있습니다"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"취소"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"건너뛰기"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"작업 표시줄 튜토리얼 패널 표시됨"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"작업 표시줄 튜토리얼 패널 닫힘"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"다음"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"뒤로"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"닫기"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"완료"</string>
</resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 8be5c53..540e255 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Өтүү аракетинин тутумдук жөндөөлөрү"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Бөлүшүү"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Бул аракетти аткарууга колдонмо же ишканаңыз тыюу салган"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Жаңсоолор үйрөткүчүн өткөрүп жибересизби?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Аны кийин <xliff:g id="NAME">%1$s</xliff:g> колдонмосунан табасыз"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Жокко чыгаруу"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Өткрп жиберүү"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Тапшырмалар тактасынын окутуу панели көрсөтүлдү"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Тапшырмалар тактасынын окутуу панели жабылды"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Кийинки"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Артка"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Жабуу"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Бүттү"</string>
</resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 915578b..b9d3c42 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ການຕັ້ງຄ່າການນຳທາງລະບົບ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"ແບ່ງປັນ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ຮູບໜ້າຈໍ"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ແອັບ ຫຼື ອົງການຂອງທ່ານບໍ່ອະນຸຍາດໃຫ້ໃຊ້ຄຳສັ່ງນີ້"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ຂ້າມການສອນການນຳໃຊ້ການນຳທາງບໍ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"ທ່ານສາມາດຊອກສ່ວນນີ້ພາຍຫຼັງໄດ້ໃນແອັບ <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ຍົກເລີກ"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ຂ້າມ"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ສະແດງການສຶກສາແຖບໜ້າວຽກແລ້ວ"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ປິດການສຶກສາແຖບໜ້າວຽກແລ້ວ"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ຕໍ່ໄປ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ກັບຄືນ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"ປິດ"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"ແລ້ວໆ"</string>
</resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index b0c679c..7a4fddc 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistemos naršymo nustatymai"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Bendrinti"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Ekrano kopija"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Tai galėsite rasti vėliau programoje „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Atšaukti"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Praleisti"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Užduočių juostos patarimai rodomi"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Užduočių juostos patarimai uždaryti"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Kitas"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Atgal"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Uždaryti"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Atlikta"</string>
</resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index df1c3e9..ee6009c 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistēmas navigācijas iestatījumi"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Kopīgot"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Veikt ekrānuzņēmumu"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Varēsiet to vēlāk atrast lietotnē <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Atcelt"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Izlaist"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Tika atvērta uzdevumjoslas apmācība"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Tika aizvērta uzdevumjoslas apmācība"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Tālāk"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Atpakaļ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Aizvērt"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Gatavs"</string>
</resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 98307da..ef03fa5 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Поставки за системска навигација"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Сподели"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Слика од екранот"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Апликацијата или вашата организација не го дозволува дејствово"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Да се прескокне упатството за навигација?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Ова може да го најдете подоцна во апликацијата <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Откажи"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Прескокни"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Се појави лентата за задачи за образование"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Затворена е лентата за задачи за образование"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Следна"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Назад"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Затвори"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
</resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index 52be3ac..a0484f4 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"സിസ്റ്റം നാവിഗേഷൻ ക്രമീകരണം"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"പങ്കിടുക"</string>
<string name="action_screenshot" msgid="8171125848358142917">"സ്ക്രീൻഷോട്ട്"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ഈ നടപടി എടുക്കുന്നത് ആപ്പോ നിങ്ങളുടെ സ്ഥാപനമോ അനുവദിക്കുന്നില്ല"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"നാവിഗേഷൻ ട്യൂട്ടോറിയൽ ഒഴിവാക്കണോ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"<xliff:g id="NAME">%1$s</xliff:g> ആപ്പിൽ നിങ്ങൾക്ക് ഇത് പിന്നീട് കാണാനാകും"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"റദ്ദാക്കുക"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ഒഴിവാക്കുക"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ടാസ്ക്ക്ബാർ വിവര പാനൽ ദൃശ്യമായി"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ടാസ്ക്ക്ബാർ വിവര പാനൽ അടച്ചു"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"അടുത്തത്"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"മടങ്ങുക"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"അടയ്ക്കുക"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"പൂർത്തിയായി"</string>
</resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 79fbca6..3500a98 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Системийн навигацын тохиргоо"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Хуваалцах"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Дэлгэцийн агшин дарах"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Энэ үйлдлийг апп эсвэл танай байгууллага зөвшөөрдөггүй"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Навигацын практик хичээлийг алгасах уу?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Та үүнийг дараа нь <xliff:g id="NAME">%1$s</xliff:g> аппаас олох боломжтой"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Цуцлах"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Алгасах"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Боловсролын ажлын талбар гарч ирсэн"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Боловсролын ажлын талбарыг хаасан"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Дараах"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Буцах"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Хаах"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Дууссан"</string>
</resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 8618f85..81342a1 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"सिस्टम नेव्हिगेशन सेटिंग्ज"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"शेअर करा"</string>
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रीनशॉट"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"अॅप किंवा तुमच्या संस्थेद्वारे ही क्रिया करण्याची अनुमती नाही"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"नेव्हिगेशन ट्युटोरियल वगळायचे आहे का?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"तुम्हाला हे नंतर <xliff:g id="NAME">%1$s</xliff:g> ॲपमध्ये मिळेल"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"रद्द करा"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"वगळा"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"टास्कबारशी संबंधित माहिती देणारे पॅनल उघडले आहे"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"टास्कबारशी संबंधित माहिती देणारे पॅनल बंद केले आहे"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"पुढे"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"मागे जा"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"बंद करा"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"पूर्ण झाले"</string>
</resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 1e2d43d..154b966 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Tetapan navigasi sistem"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Kongsi"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Tangkapan skrin"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Anda boleh mendapatkan tutorial ini kemudian dalam apl <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Batal"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Langkau"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Pendidikan bar tugas muncul"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Pendidikan bar tugas ditutup"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Seterusnya"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Kembali"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Tutup"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Selesai"</string>
</resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index b07bbfa..bf9ab62 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"စနစ် လမ်းညွှန် ဆက်တင်များ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"မျှဝေရန်"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ဤလုပ်ဆောင်ချက်ကို အက်ပ် သို့မဟုတ် သင်၏အဖွဲ့အစည်းက ခွင့်မပြုပါ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"လမ်းညွှန်ခြင်း ရှင်းလင်းပို့ချချက်ကို ကျော်မလား။"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"၎င်းကို နောက်မှ <xliff:g id="NAME">%1$s</xliff:g> အက်ပ်တွင် ရှာနိုင်သည်"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"မလုပ်တော့"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ကျော်ရန်"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ပညာရေး လုပ်ဆောင်စရာဘား ပြထားသည်"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ပညာရေး လုပ်ဆောင်စရာဘား ပိတ်ထားသည်"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ရှေ့သို့"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"နောက်သို့"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"ပိတ်ရန်"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"ပြီးပြီ"</string>
</resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 16988c5..4b1f8d0 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Innstillinger for systemnavigasjon"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Del"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skjermdump"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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 navigeringsveiledning?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du kan finne dette i <xliff:g id="NAME">%1$s</xliff:g>-appen senere"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Avbryt"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Hopp over"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Opplæringen for oppgavelinjen vises"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Opplæringen for oppgavelinjen er lukket"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Neste"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Tilbake"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Lukk"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Ferdig"</string>
</resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 91cae47..126e074 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"सिस्टम नेभिगेसनसम्बन्धी सेटिङ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"सेयर गर्नुहोस्"</string>
<string name="action_screenshot" msgid="8171125848358142917">"स्क्रिनसट"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"यो एप वा तपाईंको सङ्गठनले यो कारबाही गर्ने अनुमति दिँदैन"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"नेभिगेसन ट्युटोरियल स्किप गर्ने हो?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"तपाईं पछि <xliff:g id="NAME">%1$s</xliff:g> नामक एपमा गई यो ट्युटोरियल भेट्टाउन सक्नुहुन्छ"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"रद्द गर्नुहोस्"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"स्किप गर्नु…"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"टास्कबार एजुकेसन देखिएको छ"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"टास्कबार एजुकेसन बन्द गरिएको छ"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"अर्को"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"पछाडि"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"बन्द गर्नुहोस्"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"सम्पन्न भयो"</string>
</resources>
diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml
new file mode 100644
index 0000000..c3b2536
--- /dev/null
+++ b/quickstep/res/values-night/colors.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+
+ <color name="gesture_tutorial_back_arrow_color">#99000000</color>
+
+ <color name="gesture_tutorial_fake_wallpaper_color">#000000</color> <!-- Black -->
+
+ <color name="mock_webpage_url_bar">#202124</color>
+ <color name="mock_webpage_url_bar_item">#3c4043</color>
+
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 96da966..cfc0454 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Navigatie-instellingen van systeem"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Delen"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Je vindt dit later terug in de app <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuleren"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Overslaan"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Uitleg van taakbalk geopend"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Uitleg van taakbalk gesloten"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Volgende"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Terug"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Sluiten"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Klaar"</string>
</resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 9488021..bbf610b 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ସିଷ୍ଟମ୍ ନାଭିଗେସନ୍ ସେଟିଂସ୍"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"ସେୟାର୍ କରନ୍ତୁ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ସ୍କ୍ରିନସଟ୍"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ଆପ୍ କିମ୍ବା ଆପଣଙ୍କ ସଂସ୍ଥା ଦ୍ୱାରା ଏହି କାର୍ଯ୍ୟକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ନାଭିଗେସନ୍ ଟ୍ୟୁଟୋରିଆଲକୁ ବାଦ୍ ଦେବେ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"ଆପଣ ପରେ ଏହାକୁ <xliff:g id="NAME">%1$s</xliff:g> ଆପରେ ପାଇପାରିବେ"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ବାତିଲ୍ କରନ୍ତୁ"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ବାଦ୍ ଦିଅନ୍ତୁ"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ଟାସ୍କବାର୍ ଶିକ୍ଷା ଦେଖାଯାଇଛି"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ଟାସ୍କବାର୍ ଶିକ୍ଷା ବନ୍ଦ ହୋଇଯାଇଛି"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ପରବର୍ତ୍ତୀ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"ହୋଇଗଲା"</string>
</resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 89b2119..d7068de 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ਸਿਸਟਮ ਨੈਵੀਗੇਸ਼ਨ ਸੈਟਿੰਗਾਂ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"ਸਾਂਝਾ ਕਰੋ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ਐਪ ਜਾਂ ਤੁਹਾਡੀ ਸੰਸਥਾ ਵੱਲੋਂ ਇਸ ਕਾਰਵਾਈ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ਕੀ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਛੱਡਣਾ ਹੈ?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"ਤੁਸੀਂ ਇਸਨੂੰ ਬਾਅਦ ਵਿੱਚ <xliff:g id="NAME">%1$s</xliff:g> ਐਪ ਵਿੱਚ ਲੱਭ ਸਕਦੇ ਹੋ"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ਰੱਦ ਕਰੋ"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ਛੱਡੋ"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ਟਾਸਕਵਾਰ ਸਿੱਖਿਆ ਪੈਨਲ ਦਿਖਾਇਆ ਗਿਆ"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ਟਾਸਕਵਾਰ ਸਿੱਖਿਆ ਪੈਨਲ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ਅੱਗੇ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ਪਿੱਛੇ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"ਬੰਦ ਕਰੋ"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"ਹੋ ਗਿਆ"</string>
</resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 893db91..f054a3c 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Ustawienia nawigacji w systemie"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Udostępnij"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Zrzut ekranu"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Znajdziesz to później w aplikacji <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Anuluj"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Pomiń"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Wskazówki na temat paska zadań zostały wyświetlone"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Wskazówki na temat paska zadań zostały zamknięte"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Dalej"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Wstecz"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Zamknij"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Gotowe"</string>
</resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index c1cd2bb..269efcb 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Definições de navegação do sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Partilhar"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Fazer captura de ecrã"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Pode encontrar isto mais tarde na app <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancelar"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ignorar"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Informação da barra de tarefas apresentada"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Informação da barra de tarefas fechada"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Seguinte"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Anterior"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Fechar"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Concluir"</string>
</resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 422d13a..704eec4 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configurações de navegação do sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Compartilhar"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Capturar tela"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Veja o tutorial mais tarde no app <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Cancelar"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Pular"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"As dicas sobre a barra de tarefas foram abertas"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"As dicas sobre a barra de tarefas foram fechadas"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Próxima"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Voltar"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Fechar"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Concluído"</string>
</resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index a5e97e0..6dca9e4 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Setările de navigare ale sistemului"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Distribuiți"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Captură de ecran"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Această acțiune nu este permisă de aplicație sau de organizația dvs."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Omiteți tutorialul de navigare?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Îl puteți găsi mai târziu în aplicația <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Anulați"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Omiteți"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Panoul cu informații despre bara de activități s-a afișat"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Panoul cu informații despre bara de activități s-a închis"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Înainte"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Înapoi"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Închideți"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Gata"</string>
</resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 7c4d485..8963394 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Системные настройки навигации"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Поделиться"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Это действие заблокировано приложением или организацией."</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Пропустить руководство по жестам?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Его можно найти в приложении \"<xliff:g id="NAME">%1$s</xliff:g>\"."</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Отмена"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Пропустить"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Обучение по работе с панелью задач показано"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Обучение по работе с панелью задач скрыто"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Далее"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Назад"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Закрыть"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
</resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 5f322e6..1182153 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"පද්ධති සංචාලන සැකසීම්"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"බෙදා ගන්න"</string>
<string name="action_screenshot" msgid="8171125848358142917">"තිර රුව"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"මෙම ක්රියාව යෙදුම හෝ ඔබේ සංවිධානය මගින් ඉඩ නොදේ"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"නිබන්ධනය සංචාලනය මඟ හරින්නද?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"ඔබට මෙය පසුව <xliff:g id="NAME">%1$s</xliff:g> යෙදුම තුළ සොයා ගත හැකිය"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"අවලංගු කරන්න"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"මඟ හරින්න"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"කාර්ය තීරු අධ්යාපනය දිස් විය"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"කාර්ය තීරු අධ්යාපනය වසා ඇත"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ඊළඟ"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"ආපසු"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"වසන්න"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"නිමයි"</string>
</resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 799e941..2cd414b 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Nastavenia navigácie systémom"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Zdieľať"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snímka obrazovky"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Tento návod nájdete v aplikácii <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Zrušiť"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Preskočiť"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Zobrazila sa výuka k hlavnému panelu"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Výuka k hlavnému panelu bola zatvorená"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Ďalej"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Späť"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Zavrieť"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Hotovo"</string>
</resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 7e40277..5edc2bb 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Nastavitve krmarjenja po sistemu"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Deli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Posnetek zaslona"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"To lahko pozneje najdete v aplikaciji <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Prekliči"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Preskoči"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Poučni nasveti o opravilni vrstici so prikazani."</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Poučni nasveti o opravilni vrstici so zaprti."</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Naprej"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Nazaj"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Zapri"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Končano"</string>
</resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 86528d2..2b36b5c 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Cilësimet e navigimit të sistemit"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Ndaj"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Pamja e ekranit"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Këtë mund ta gjesh më vonë tek aplikacioni <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Anulo"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Kapërce"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Edukimi i shiritit të detyrave u shfaq"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Edukimi nga shiriti i detyrave u mbyll"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Para"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Pas"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Mbyll"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"U krye"</string>
</resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index f2ffa0a..3289011 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Подешавања кретања кроз систем"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Дели"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Снимак екрана"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Апликација или организација не дозвољавају ову радњу"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Желите да прескочите водич за кретање?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Можете да пронађете ово касније у апликацији <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Откажи"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Прескочи"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Едукативно окно из траке задатака се појавило"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Едукативно окно из траке задатака је затворено"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Даље"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Назад"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Затвори"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
</resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 931e458..4e3e7ac 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Systemnavigeringsinställningar"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Dela"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skärmbild"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du hittar det här igen i <xliff:g id="NAME">%1$s</xliff:g>-appen"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Avbryt"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Hoppa över"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Information om aktivitetsfältet visades"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Information om aktivitetsfältet stängdes"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Nästa"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Tillbaka"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Stäng"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Klar"</string>
</resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 22aa3ef..e933755 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Mipangilio ya usogezaji kwenye mfumo"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Shiriki"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Picha ya skrini"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Utapata mafunzo haya baadaye katika programu ya <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Ghairi"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ruka"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Paneli ya elimu kwenye upau wa shughuli inaonyeshwa"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Paneli ya elimu kwenye upau wa shughuli imefungwa"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Endelea"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Nyuma"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Funga"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Imemaliza"</string>
</resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index de7c26d..4551e2b 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"சிஸ்டம் வழிசெலுத்தல் அமைப்புகள்"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"பகிர்"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ஸ்கிரீன்ஷாட்"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ஆப்ஸோ உங்கள் நிறுவனமோ இந்த செயலை அனுமதிப்பதில்லை"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"வழிகாட்டுதல் பயிற்சியைத் தவிர்க்கவா?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"<xliff:g id="NAME">%1$s</xliff:g> ஆப்ஸில் பிறகு இதைக் கண்டறியலாம்"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ரத்துசெய்"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"தவிர்"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"பணிப்பட்டியை எவ்வாறு பயன்படுத்துவது என்பது பற்றிய பலகம் காட்டப்படுகிறது"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"பணிப்பட்டியை எவ்வாறு பயன்படுத்துவது என்பது பற்றிய பலகம் மூடப்பட்டது"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"அடுத்து"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"பின்செல்"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"மூடுக"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"முடிந்தது"</string>
</resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 73e4e0c..b7cb7af 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -33,13 +33,13 @@
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"మీ సూచించబడిన యాప్లు"</string>
<string name="hotseat_edu_title_migrate" msgid="306578144424489980">"మీ మొదటి స్క్రీన్ దిగువ వరుసలో యాప్ సలహాలను పొందండి"</string>
<string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"మీ హోమ్ స్క్రీన్లోని ఇష్టమైన వాటి వరుసలో యాప్ సూచనలు పొందండి"</string>
- <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు మీ మొదటి స్క్రీన్ పైకి చేరుకుంటాయి."</string>
- <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్లు మీ మొదటి స్క్రీన్కు చేరుకుంటాయి."</string>
- <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ కార్యకలాపాలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు కొత్త ఫోల్డర్కు తరలించబడతాయి."</string>
+ <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు మీ మొదటి స్క్రీన్ పైకి చేరుకుంటాయి."</string>
+ <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. ఇష్టమైన వాటి వరుసలోని యాప్లు మీ మొదటి స్క్రీన్కు చేరుకుంటాయి."</string>
+ <string name="hotseat_edu_message_migrate_alt" msgid="3042360119039646356">"మీరు ఎక్కువగా ఉపయోగించే యాప్లను నేరుగా మొదటి స్క్రీన్లోనే సులభంగా యాక్సెస్ చేయండి. మీ రోజువారీ యాక్టివిటీలను బట్టి సూచనలు మారతాయి. దిగువ వరుసలోని యాప్లు కొత్త ఫోల్డర్కు తరలించబడతాయి."</string>
<string name="hotseat_edu_accept" msgid="1611544083278999837">"యాప్ సూచనలను పొందండి"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"వద్దు"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"సెట్టింగ్లు"</string>
- <string name="hotseat_auto_enrolled" msgid="522100018967146807">"ఎక్కువగా ఉపయోగించిన యాప్లు ఇక్కడ కనిపిస్తాయి, అవి రోజువారీ కార్యకలాపాలను బట్టి మారుతూ ఉంటాయి"</string>
+ <string name="hotseat_auto_enrolled" msgid="522100018967146807">"ఎక్కువగా ఉపయోగించిన యాప్లు ఇక్కడ కనిపిస్తాయి, అవి రోజువారీ యాక్టివిటీలను బట్టి మారుతూ ఉంటాయి"</string>
<string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"యాప్ సలహాలను పొందడానికి దిగువ వరుస నుండి యాప్లను లాగండి"</string>
<string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"యాప్ సూచనలు ఖాళీ స్పేస్కు జోడించబడ్డాయి"</string>
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి"</string>
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"సిస్టమ్ నావిగేషన్ సెట్టింగ్లు"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"షేర్ చేయండి"</string>
<string name="action_screenshot" msgid="8171125848358142917">"స్క్రీన్షాట్"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ఈ చర్యను యాప్ గానీ, మీ సంస్థ గానీ అనుమతించవు"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"నావిగేషన్ ట్యుటోరియల్ను స్కిప్ చేయాలా?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"<xliff:g id="NAME">%1$s</xliff:g> యాప్లో మీరు తర్వాత కనుగొనవచ్చు"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"రద్దు చేయి"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"స్కిప్ చేయి"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"టాస్క్బార్ శిక్షణకు సంబంధించిన ప్యానెల్ కనిపించింది"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"టాస్క్బార్ శిక్షణకు సంబంధించిన ప్యానెల్ మూసివేయబడింది"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"తర్వాత"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"వెనుకకు"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"మూసివేయండి"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"పూర్తయింది"</string>
</resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 52b2878..4205a62 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"การตั้งค่าการนำทางของระบบ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"แชร์"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ภาพหน้าจอ"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"แอปหรือองค์กรของคุณไม่อนุญาตการดำเนินการนี้"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"ข้ามบทแนะนำการนำทางไหม"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"คุณดูบทแนะนำนี้ได้ภายหลังในแอป \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"ยกเลิก"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"ข้าม"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"แถบงาน Education ปรากฎขึ้น"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ปิดแถบงาน Education แล้ว"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"ถัดไป"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"กลับ"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"ปิด"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"เสร็จ"</string>
</resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 412449b..86787a8 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Mga setting ng navigation ng system"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Ibahagi"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Makikita mo ito sa <xliff:g id="NAME">%1$s</xliff:g> app sa ibang pagkakataon"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Kanselahin"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Laktawan"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Lumabas ang edukasyon sa taskbar"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Sarado ang edukasyon sa taskbar"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Susunod"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Bumalik"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Isara"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Tapos na"</string>
</resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 58d78e0..9f51423 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistem gezinme ayarları"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Paylaş"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Ekran görüntüsü"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Bunu daha sonra <xliff:g id="NAME">%1$s</xliff:g> uygulamasında bulabilirsiniz"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"İptal"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Atla"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Görev çubuğu eğitimi görüntülendi"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Görev çubuğu eğitimi kapatıldı"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"İleri"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Geri"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Kapat"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Bitti"</string>
</resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 544cec7..12119de 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Системні налаштування навігації"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Поділитися"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Знімок екрана"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"Ця дія заборонена додатком або адміністратором організації"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Пропустити посібник із навігації?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Ви знайдете його пізніше в додатку <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Скасувати"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Пропустити"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Панель завдань Education відкрито"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Панель завдань Education закрито"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Далі"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Назад"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Закрити"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Готово"</string>
</resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 2e8bde9..0a62055 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"سسٹم نیویگیشن کی ترتیبات"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"اشتراک کریں"</string>
<string name="action_screenshot" msgid="8171125848358142917">"اسکرین شاٹ"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"ایپ یا آپ کی تنظیم کی جانب سے اس کارروائی کی اجازت نہیں ہے"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"نیویگیشن کا ٹیوٹوریل نظر انداز کریں؟"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"آپ اسے بعد میں <xliff:g id="NAME">%1$s</xliff:g> ایپ میں تلاش کر سکتے ہیں"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"منسوخ کریں"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"نظر انداز کریں"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"ٹاکس بار کا تعلیمی پینل ظاہر ہو گیا"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"ٹاسک بار کا تعلیمی پینل بند ہو گیا"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"آگے"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"پیچھے"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"بند کریں"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"ہو گیا"</string>
</resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 8b194de..dcd413f 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Tizim navigatsiya sozlamalari"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Ulashish"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Skrinshot"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Bu darslar <xliff:g id="NAME">%1$s</xliff:g> ilovasida chiqadi"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Bekor qilish"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Tashlab ketish"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Taʼlim vazifalar paneli chiqdi"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Taʼlim vazifalar paneli yopildi"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Keyingisi"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Orqaga"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Yopish"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Tayyor"</string>
</resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 7bd0c70..304fe7c 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Chế độ cài đặt di chuyển trên hệ thống"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Chia sẻ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Chụp ảnh màn hình"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Bạn có thể tìm lại phần hướng dẫn này trong ứng dụng <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Hủy"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Bỏ qua"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Đã hiện bảng hướng dẫn trên thanh tác vụ"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Đã đóng bảng hướng dẫn trên thanh tác vụ"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Tiếp theo"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Quay lại"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Đóng"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Xong"</string>
</resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 9e641de..eb4ae78 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"系统导航设置"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"分享"</string>
<string name="action_screenshot" msgid="8171125848358142917">"屏幕截图"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"该应用或您所在的单位不允许执行此操作"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"要跳过导航教程吗?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"您之后可以在“<xliff:g id="NAME">%1$s</xliff:g>”应用中找到此教程"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"取消"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"跳过"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"任务栏教程已显示"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"任务栏教程已关闭"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"继续"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"返回"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"关闭"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
</resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index d1e8392..4a0d146 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"系統導覽設定"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"分享"</string>
<string name="action_screenshot" msgid="8171125848358142917">"螢幕截圖"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"應用程式或您的機構不允許此操作"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"要略過手勢操作教學課程嗎?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"您之後可以在「<xliff:g id="NAME">%1$s</xliff:g>」應用程式找到這些說明"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"取消"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"略過"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"顯示咗工作列教學"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"閂咗工作列教學"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"繼續"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"返回"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"關閉"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
</resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index db10354..f73d533 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"系統操作機制設定"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"分享"</string>
<string name="action_screenshot" msgid="8171125848358142917">"螢幕截圖"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<string name="blocked_by_policy" msgid="2071401072261365546">"這個應用程式或貴機構不允許執行這個動作"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"要略過手勢操作教學課程嗎?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"你之後可以在「<xliff:g id="NAME">%1$s</xliff:g>」應用程式找到這些說明"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"取消"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"略過"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"工作列教學課程已顯示"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"工作列教學課程已關閉"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"繼續"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"返回"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"關閉"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
</resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index bbfb840..076712a 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -79,9 +79,19 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Amasethingi wokuzulazula isistimu"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Yabelana"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Isithombe-skrini"</string>
+ <!-- no translation found for action_split (2098009717623550676) -->
+ <skip />
<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>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Lokhu ungakuthola kamuva ku-app ye-<xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Khansela"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Yeqa"</string>
+ <string name="taskbar_edu_opened" msgid="3950252793551919129">"Imfuno yebha yomsebenzi ivelile"</string>
+ <string name="taskbar_edu_closed" msgid="126643734478892862">"Imfundo yebha yomsebenzi ivaliwe"</string>
+ <!-- no translation found for taskbar_edu_stashing (5212374387411764031) -->
+ <skip />
+ <string name="taskbar_edu_next" msgid="4007618274426775841">"Okulandelayo"</string>
+ <string name="taskbar_edu_previous" msgid="459202320127201702">"Emuva"</string>
+ <string name="taskbar_edu_close" msgid="887022990168191073">"Vala"</string>
+ <string name="taskbar_edu_done" msgid="6880178093977704569">"Kwenziwe"</string>
</resources>
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 2f24441..4755292 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -14,8 +14,6 @@
limitations under the License.
-->
<resources>
- <color name="back_arrow_color_light">#FFFFFFFF</color>
- <color name="back_arrow_color_dark">#99000000</color>
<color name="chip_hint_foreground_color">#fff</color>
<color name="chip_scrim_start_color">#39000000</color>
@@ -28,4 +26,43 @@
<!-- Taskbar -->
<color name="taskbar_background">@color/overview_scrim_dark</color>
<color name="taskbar_icon_selection_ripple">#E0E0E0</color>
+
+ <color name="taskbar_stashed_handle_light_color">#EBffffff</color>
+ <color name="taskbar_stashed_handle_dark_color">#99000000</color>
+
+ <!-- Gesture navigation tutorial -->
+ <color name="gesture_tutorial_back_arrow_color">#FFFFFFFF</color>
+
+ <color name="gesture_tutorial_fake_wallpaper_color">#f9f9f9</color> <!-- White -->
+ <color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
+ <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
+ <!-- Must contrast gesture_tutorial_fake_wallpaper_color -->
+ <color name="gesture_tutorial_fake_previous_task_view_color">#3C4043</color> <!-- Gray -->
+ <color name="gesture_tutorial_action_button_label_color">#FF000000</color>
+ <color name="gesture_tutorial_primary_color">#B7F29F</color> <!-- Light Green -->
+
+ <!-- Mock conversation -->
+ <color name="mock_conversation_background">#f1f3f4</color>
+ <color name="mock_conversation_top_bar">#e8eaed</color>
+ <color name="mock_conversation_top_bar_item">#dadce0</color>
+ <color name="mock_conversation_sent_message">#bdc1c6</color>
+ <color name="mock_conversation_received_message">#e8eaed</color>
+ <color name="mock_conversation_message_input">#dadce0</color>
+ <color name="mock_conversation_profile_icon">#dadce0</color>
+
+ <!-- Mock conversations list -->
+ <color name="mock_list_background">#dadce0</color>
+ <color name="mock_list_top_bar">#e8eaed</color>
+ <color name="mock_list_top_bar_item">#f8f9fa</color>
+ <color name="mock_list_profile_icon">#9aa0a6</color>
+ <color name="mock_list_preview_message">#bdc1c6</color>
+ <color name="mock_list_button">#bdc1c6</color>
+
+ <!-- Mock web page -->
+ <color name="mock_webpage_background">#f1f3f4</color>
+ <color name="mock_webpage_url_bar">#6e7175</color>
+ <color name="mock_webpage_url_bar_item">#9a9a9a</color>
+ <color name="mock_webpage_top_bar">#e8eaed</color>
+ <color name="mock_webpage_top_bar_item">#80868b</color>
+ <color name="mock_webpage_page_text">#bdc1c6</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d67b23b..31c0f5f 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -30,6 +30,7 @@
determines how many thumbnails will be fetched in the background. -->
<integer name="recentsThumbnailCacheSize">3</integer>
<integer name="recentsIconCacheSize">12</integer>
+ <integer name="recentsScrollHapticMinGapMillis">20</integer>
<!-- Assistant Gesture -->
<integer name="assistant_gesture_min_time_threshold">200</integer>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 659ff9a..c649082 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -32,7 +32,8 @@
<dimen name="overview_minimum_next_prev_size">50dp</dimen>
<dimen name="overview_task_margin">16dp</dimen>
- <dimen name="overview_task_margin_grid">12dp</dimen>
+ <dimen name="overview_task_margin_focused">12dp</dimen>
+ <dimen name="overview_task_margin_grid">4dp</dimen>
<!-- Overrideable in overlay that provides the Overview Actions. -->
<dimen name="overview_actions_height">48dp</dimen>
@@ -45,8 +46,8 @@
<dimen name="overview_actions_horizontal_margin">16dp</dimen>
<dimen name="overview_grid_side_margin">50dp</dimen>
- <dimen name="overview_grid_row_spacing_portrait">37.13dp</dimen>
- <dimen name="overview_grid_row_spacing_landscape">33.38dp</dimen>
+ <dimen name="overview_grid_row_spacing_portrait">17.13dp</dimen>
+ <dimen name="overview_grid_row_spacing_landscape">13.38dp</dimen>
<dimen name="overview_grid_focus_vertical_margin">0dp</dimen>
<!-- These speeds are in dp/s -->
@@ -156,13 +157,17 @@
<dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
<!-- Taskbar -->
- <dimen name="taskbar_size">60dp</dimen>
+ <dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
<dimen name="taskbar_icon_touch_size">48dp</dimen>
<dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
<dimen name="taskbar_folder_margin">16dp</dimen>
<dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
<dimen name="taskbar_nav_buttons_size">48dp</dimen>
+ <dimen name="taskbar_contextual_button_margin">16dp</dimen>
+ <dimen name="taskbar_contextual_buttons_size">35dp</dimen>
<dimen name="taskbar_stashed_size">24dp</dimen>
<dimen name="taskbar_stashed_handle_width">220dp</dimen>
<dimen name="taskbar_stashed_handle_height">6dp</dimen>
+ <dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
+ <dimen name="taskbar_edu_wave_anim_trans_y_return_overshoot">4dp</dimen>
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 4aee2a9..7158287 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -191,6 +191,8 @@
<string name="action_share">Share</string>
<!-- Label for a button that causes a screen shot of the current app to be taken. [CHAR_LIMIT=40] -->
<string name="action_screenshot">Screenshot</string>
+ <!-- Label for a button that enters split screen selection mode. [CHAR_LIMIT=20] -->
+ <string name="action_split">Split</string>
<!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
<string name="blocked_by_policy">This action isn\'t allowed by the app or your organization</string>
@@ -203,4 +205,27 @@
<string name="gesture_tutorial_action_button_label_cancel">Cancel</string>
<!-- Button text shown on a button on the tutorial skip dialog to exit the tutorial. [CHAR LIMIT=14] -->
<string name="gesture_tutorial_action_button_label_skip">Skip</string>
+
+ <!-- ******* Taskbar Edu ******* -->
+ <!-- Accessibility text spoken when the taskbar education panel appears [CHAR_LIMIT=NONE] -->
+ <string name="taskbar_edu_opened">Taskbar education appeared</string>
+ <!-- Accessibility text spoken when the taskbar education panel disappears [CHAR_LIMIT=NONE] -->
+ <string name="taskbar_edu_closed">Taskbar education closed</string>
+ <!-- Text in dialog that lets a user know how they can use the taskbar to switch apps on their device.
+ [CHAR_LIMIT=60] -->
+ <string name="taskbar_edu_switch_apps" translatable="false">Use the taskbar to switch apps</string>
+ <!-- Text in dialog that lets a user know how they can use the taskbar to use multiple apps at once on their device.
+ [CHAR_LIMIT=60] -->
+ <string name="taskbar_edu_splitscreen" translatable="false">Drag to the side to use two apps at once</string>
+ <!-- Text in dialog that lets a user know how they can hide the taskbar on their device.
+ [CHAR_LIMIT=60] -->
+ <string name="taskbar_edu_stashing">Touch & hold to hide the taskbar</string>
+ <!-- Text on button to go to the next screen of a tutorial [CHAR_LIMIT=16] -->
+ <string name="taskbar_edu_next">Next</string>
+ <!-- Text on button to go to the previous screen of a tutorial [CHAR_LIMIT=16] -->
+ <string name="taskbar_edu_previous">Back</string>
+ <!-- Text on button to exit a tutorial [CHAR_LIMIT=16] -->
+ <string name="taskbar_edu_close">Close</string>
+ <!-- Text on button to finish a tutorial [CHAR_LIMIT=16] -->
+ <string name="taskbar_edu_done">Done</string>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 07c448d..b5444b5 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -135,8 +135,39 @@
<item name="android:textAllCaps">false</item>
</style>
+ <style name="OverviewClearAllButton" parent="@android:style/Widget.DeviceDefault.Button">
+ <item name="android:background">@drawable/bg_overview_clear_all_button</item>
+ <item name="android:minWidth">85dp</item>
+ <item name="android:minHeight">36dp</item>
+ <item name="android:stateListAnimator">@null</item>
+ </style>
+
<!-- Icon displayed on the taskbar -->
<style name="BaseIcon.Workspace.Taskbar" >
<item name="iconDisplay">taskbar</item>
</style>
+
+ <style name="TaskbarEdu.Button.Close" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/button_taskbar_edu_bordered</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
+ <style name="TaskbarEdu.Button.Next" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/button_taskbar_edu_colored</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
+ <style name="TextAppearance.TaskbarEdu.Title"
+ parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" >
+ <item name="android:layout_marginHorizontal">16dp</item>
+ <item name="android:gravity">center_horizontal</item>
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">24sp</item>
+ <item name="android:lines">2</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
deleted file mode 100644
index 9df9ab1..0000000
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 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.launcher3.util.LauncherUIHelper.doLayout;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-
-import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.controller.ActivityController;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
-import org.robolectric.shadows.ShadowLooper;
-import org.robolectric.util.ReflectionHelpers;
-
-
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
-@org.junit.Ignore
-public class RecentsActivityTest {
-
- @Test
- public void testRecentsActivityCreates() {
- ActivityController<RecentsActivity> controller =
- Robolectric.buildActivity(RecentsActivity.class);
-
- RecentsActivity launcher = controller.setup().get();
- doLayout(launcher);
-
- // TODO: Ensure that LauncherAppState is not created
- }
-
- @Test
- public void testRecents_showCurrentTask() {
- ActivityController<RecentsActivity> controller =
- Robolectric.buildActivity(RecentsActivity.class);
-
- RecentsActivity activity = controller.setup().get();
- doLayout(activity);
-
- FallbackRecentsView frv = activity.getOverviewPanel();
-
- RunningTaskInfo placeholderTask = new RunningTaskInfo();
- placeholderTask.taskId = 22;
- frv.showCurrentTask(placeholderTask);
- doLayout(activity);
-
- ThumbnailData thumbnailData = new ThumbnailData();
- ReflectionHelpers.setField(thumbnailData, "thumbnail",
- Bitmap.createBitmap(300, 500, Config.ARGB_8888));
- frv.switchToScreenshot(thumbnailData, () -> { });
- ShadowLooper.idleMainLooper();
- }
-}
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
deleted file mode 100644
index fd93d98..0000000
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2020 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.Display.DEFAULT_DISPLAY;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.display.DisplayManager;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.shadows.LShadowDisplay;
-import com.android.launcher3.util.DisplayController;
-import com.android.quickstep.LauncherActivityInterface;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeMatcher;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowDisplayManager;
-
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
-public class TaskViewSimulatorTest {
-
- @Test
- public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
- new TaskMatrixVerifier()
- .withLauncherSize(1200, 2450)
- .withInsets(new Rect(0, 80, 0, 120))
- .verifyNoTransforms();
- }
-
- @Test
- public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
- new TaskMatrixVerifier()
- .withLauncherSize(1200, 2450)
- .withInsets(new Rect(55, 80, 55, 120))
- .verifyNoTransforms();
- }
-
- @Test
- public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
- new TaskMatrixVerifier()
- .withLauncherSize(2450, 1250)
- .withInsets(new Rect(0, 80, 0, 40))
- .verifyNoTransforms();
- }
-
- @Test
- public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
- new TaskMatrixVerifier()
- .withLauncherSize(2450, 1250)
- .withInsets(new Rect(0, 80, 120, 0))
- .verifyNoTransforms();
- }
-
- @Test
- public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
- new TaskMatrixVerifier()
- .withLauncherSize(2450, 1250)
- .withInsets(new Rect(55, 80, 55, 120))
- .verifyNoTransforms();
- }
-
- @Test
- public void taskProperlyScaled_landscape_rotated() {
- new TaskMatrixVerifier()
- .withLauncherSize(1200, 2450)
- .withInsets(new Rect(0, 80, 0, 120))
- .withAppBounds(
- new Rect(0, 0, 2450, 1200),
- new Rect(0, 80, 0, 120),
- Surface.ROTATION_90)
- .verifyNoTransforms();
- }
-
- private static class TaskMatrixVerifier extends TransformParams {
-
- private final Context mContext = RuntimeEnvironment.application;
-
- private Rect mAppBounds = new Rect();
- private Rect mLauncherInsets = new Rect();
-
- private Rect mAppInsets;
-
- private int mAppRotation = -1;
- private DeviceProfile mDeviceProfile;
-
- TaskMatrixVerifier withLauncherSize(int width, int height) {
- ShadowDisplayManager.changeDisplay(DEFAULT_DISPLAY,
- String.format("w%sdp-h%sdp-mdpi", width, height));
- if (mAppBounds.isEmpty()) {
- mAppBounds.set(0, 0, width, height);
- }
- return this;
- }
-
- TaskMatrixVerifier withInsets(Rect insets) {
- LShadowDisplay shadowDisplay = Shadow.extract(
- mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
- shadowDisplay.setInsets(insets);
- mLauncherInsets.set(insets);
- return this;
- }
-
- TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
- mAppBounds.set(bounds);
- mAppInsets = insets;
- mAppRotation = appRotation;
- return this;
- }
-
- void verifyNoTransforms() {
- mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext)
- .getDeviceProfile(mContext);
- mDeviceProfile.updateInsets(mLauncherInsets);
-
- TaskViewSimulator tvs = new TaskViewSimulator(mContext,
- LauncherActivityInterface.INSTANCE);
- tvs.setDp(mDeviceProfile);
-
- int launcherRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
- if (mAppRotation < 0) {
- mAppRotation = launcherRotation;
- }
- tvs.getOrientationState().update(launcherRotation, mAppRotation);
- if (mAppInsets == null) {
- mAppInsets = new Rect(mLauncherInsets);
- }
- tvs.setPreviewBounds(mAppBounds, mAppInsets);
-
- tvs.fullScreenProgress.value = 1;
- tvs.recentsViewScale.value = tvs.getFullScreenScale();
- tvs.apply(this);
- }
-
- @Override
- public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
- SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
- proxy.onBuildTargetParams(builder, mock(RemoteAnimationTargetCompat.class), this);
- return new SurfaceParams[] {builder.build()};
- }
-
- @Override
- public void applySurfaceParams(SurfaceParams[] params) {
- // Verify that the task position remains the same
- RectF newAppBounds = new RectF(mAppBounds);
- params[0].matrix.mapRect(newAppBounds);
- Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
-
- System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
- }
- }
-
- private static class AlmostSame extends TypeSafeMatcher<RectF> {
-
- // Allow 1px error margin to account for float to int conversions
- private final float mError = 1f;
- private final Rect mExpected;
-
- AlmostSame(Rect expected) {
- mExpected = expected;
- }
-
- @Override
- protected boolean matchesSafely(RectF item) {
- return Math.abs(item.left - mExpected.left) < mError
- && Math.abs(item.top - mExpected.top) < mError
- && Math.abs(item.right - mExpected.right) < mError
- && Math.abs(item.bottom - mExpected.bottom) < mError;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendValue(mExpected);
- }
- }
-}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index b4b29aa..2534699 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -35,6 +35,8 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.ServiceConnection;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -73,6 +75,8 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.LauncherUnfoldAnimationController;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.SplitSelectStateController;
@@ -82,6 +86,9 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.UnfoldTransitionFactory;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import java.util.List;
import java.util.stream.Stream;
@@ -117,6 +124,7 @@
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
+
Log.d(TAG, "TIS service connected");
resetServiceBindRetryState();
@@ -142,16 +150,41 @@
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
private SplitPlaceholderView mSplitPlaceholderView;
+ private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+ private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
addMultiWindowModeChangedListener(mDepthController);
+ initUnfoldTransitionProgressProvider();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onResume();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onPause();
+ }
+
+ super.onPause();
}
@Override
public void onDestroy() {
mAppTransitionManager.onActivityDestroyed();
+ if (mUnfoldTransitionProgressProvider != null) {
+ mUnfoldTransitionProgressProvider.destroy();
+ }
SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
@@ -160,6 +193,11 @@
mTaskbarManager.clearLauncher(this);
}
resetServiceBindRetryState();
+
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onDestroy();
+ }
+
super.onDestroy();
}
@@ -297,7 +335,7 @@
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+ new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this));
overviewPanel.init(mActionsView, controller);
mActionsView.setDp(getDeviceProfile());
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
@@ -334,6 +372,28 @@
mConnectionAttempts = 0;
}
+ private void initUnfoldTransitionProgressProvider() {
+ final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+ if (config.isEnabled()) {
+ mUnfoldTransitionProgressProvider =
+ UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+ this,
+ config,
+ ProxyScreenStatusProvider.INSTANCE,
+ getSystemService(DeviceStateManager.class),
+ getSystemService(SensorManager.class),
+ getMainThreadHandler(),
+ getMainExecutor()
+ );
+
+ mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
+ this,
+ getWindowManager(),
+ mUnfoldTransitionProgressProvider
+ );
+ }
+ }
+
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
mTaskbarUIController = taskbarUIController;
}
@@ -373,6 +433,11 @@
return mTaskbarStateHandler;
}
+ @Nullable
+ public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
+ return mUnfoldTransitionProgressProvider;
+ }
+
@Override
public boolean supportsAdaptiveIconAnimation(View clickedView) {
return mAppTransitionManager.hasControlRemoteAppTransitionPermission()
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 2da8a45..89cc1f6 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -32,7 +32,6 @@
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_SCRIM_FOR_APP_LAUNCH;
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
@@ -71,6 +70,7 @@
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
+import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -85,6 +85,7 @@
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.RunnableList;
@@ -141,21 +142,11 @@
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
- private static final long APP_LAUNCH_DURATION = 450;
- // Use a shorter duration for x or y translation to create a curve effect
- private static final long APP_LAUNCH_CURVED_DURATION = 250;
+ private 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;
- // We scale the durations for the downward app launch animations (minus the scale animation).
- private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
- private static final long APP_LAUNCH_DOWN_DURATION =
- (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
- private static final long APP_LAUNCH_DOWN_CURVED_DURATION =
- (long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
- private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
- (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
-
public static final int ANIMATION_NAV_FADE_IN_DURATION = 266;
public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133;
public static final long ANIMATION_DELAY_NAV_FADE_IN =
@@ -165,9 +156,6 @@
public static final Interpolator NAV_FADE_OUT_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 1f, 1f);
- private static final long CROP_DURATION = 375;
- private static final long RADIUS_DURATION = 375;
-
public static final int RECENTS_LAUNCH_DURATION = 336;
private static final int LAUNCHER_RESUME_START_DELAY = 100;
private static final int CLOSING_TRANSITION_DURATION_MS = 250;
@@ -221,6 +209,9 @@
// Will never be larger than MAX_NUM_TASKS
private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
+ private final Interpolator mOpeningXInterpolator;
+ private final Interpolator mOpeningInterpolator;
+
public QuickstepTransitionManager(Context context) {
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mDragLayer = mLauncher.getDragLayer();
@@ -247,6 +238,10 @@
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
mStartingWindowListener);
}
+
+ mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
+ mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
+ R.interpolator.three_point_fast_out_extra_slow_in);
}
@Override
@@ -427,6 +422,10 @@
4 - rotationChange);
}
}
+ // TODO(b/196637509): don't do this for immersive apps.
+ if (mDeviceProfile.isTaskbarPresentInApps) {
+ bounds.bottom -= mDeviceProfile.taskbarSize;
+ }
return bounds;
}
@@ -511,7 +510,10 @@
final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get();
if (scrimEnabled) {
- int scrimColor = Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor);
+ boolean useTaskbarColor = mDeviceProfile.isTaskbarPresentInApps;
+ int scrimColor = useTaskbarColor
+ ? mLauncher.getResources().getColor(R.color.taskbar_background)
+ : Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor);
int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0);
int[] colors = isAppOpening
? new int[]{scrimColorTrans, scrimColor}
@@ -524,6 +526,30 @@
colors);
scrim.setDuration(CONTENT_SCRIM_DURATION);
scrim.setInterpolator(DEACCEL_1_5);
+
+ if (useTaskbarColor) {
+ // Hide the taskbar background color since it would duplicate the scrim.
+ scrim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ mLauncher.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ taskbarUIController.forceHideBackground(true);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ mLauncher.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ taskbarUIController.forceHideBackground(false);
+ }
+ }
+ });
+ }
+
launcherAnimator.play(scrim);
}
}
@@ -638,6 +664,10 @@
if (v instanceof BubbleTextView) {
((BubbleTextView) v).setStayPressed(false);
}
+ LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+ if (taskbarController != null) {
+ taskbarController.showEdu();
+ }
openingTargets.release();
}
});
@@ -646,31 +676,33 @@
? Math.max(crop.width(), crop.height()) / 2f
: 0f;
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
- FloatProp mDx = new FloatProp(0, prop.dX, 0, prop.xDuration, AGGRESSIVE_EASE);
- FloatProp mDy = new FloatProp(0, prop.dY, 0, prop.yDuration, AGGRESSIVE_EASE);
+ FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION,
+ mOpeningXInterpolator);
+ FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION,
+ mOpeningInterpolator);
FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,
- prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE);
+ prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f,
- APP_LAUNCH_ALPHA_START_DELAY, prop.alphaDuration, LINEAR);
+ APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR);
FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,
- RADIUS_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,
- APP_LAUNCH_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
- 0, CROP_DURATION, EXAGGERATED_EASE);
+ 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,
- 0, CROP_DURATION, EXAGGERATED_EASE);
+ 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0,
- CROP_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0,
- CROP_DURATION, EXAGGERATED_EASE);
+ APP_LAUNCH_DURATION, mOpeningInterpolator);
FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
NAV_FADE_OUT_INTERPOLATOR);
@@ -835,7 +867,7 @@
}
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
v, widgetBackgroundBounds,
new Size(windowTargetBounds.width(), windowTargetBounds.height()),
@@ -872,22 +904,23 @@
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
- 0 /* start */, RADIUS_DURATION, LINEAR);
- final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, RADIUS_DURATION, LINEAR);
+ 0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator);
+ final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION,
+ mOpeningInterpolator);
// Window & widget background positioning bounds
final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
- windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_CURVED_DURATION,
- EXAGGERATED_EASE);
+ windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION,
+ mOpeningXInterpolator);
final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION,
- EXAGGERATED_EASE);
+ mOpeningInterpolator);
final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION,
- EXAGGERATED_EASE);
+ mOpeningInterpolator);
final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION,
- EXAGGERATED_EASE);
+ mOpeningInterpolator);
final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
NAV_FADE_OUT_INTERPOLATOR);
@@ -1091,6 +1124,7 @@
if (target.mode == mode && target.taskInfo != null
// Compare component name instead of task-id because transitions will promote
// the target up to the root task while getTaskId returns the leaf.
+ && target.taskInfo.topActivity != null
&& target.taskInfo.topActivity.equals(mLauncher.getComponentName())) {
return true;
}
@@ -1115,7 +1149,7 @@
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
- QuickStepContract.getWindowCornerRadius(mLauncher.getResources());
+ QuickStepContract.getWindowCornerRadius(mLauncher);
unlockAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -1157,7 +1191,7 @@
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
float windowCornerRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
@@ -1431,10 +1465,6 @@
public final float dX;
public final float dY;
- public final long xDuration;
- public final long yDuration;
- public final long alphaDuration;
-
public final float initialAppIconScale;
public final float finalAppIconScale;
@@ -1466,14 +1496,6 @@
dX = centerX - launcherIconBounds.centerX();
dY = centerY - launcherIconBounds.centerY();
- boolean useUpwardAnimation = launcherIconBounds.top > centerY
- || Math.abs(dY) < dp.cellHeightPx;
- xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION
- : APP_LAUNCH_DOWN_DURATION;
- yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION
- : APP_LAUNCH_DOWN_CURVED_DURATION;
- alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
- : APP_LAUNCH_ALPHA_DOWN_DURATION;
iconAlphaStart = hasSplashScreen && !hasDifferentAppIcon ? 0 : 1f;
final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size",
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
deleted file mode 100644
index 3a7d821..0000000
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.hybridhotseat;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityTracker;
-
-/**
- * Proxy activity to return user to home screen and show halfsheet education
- */
-public class HotseatEduActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent homeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- Launcher.ACTIVITY_TRACKER.registerCallback(new HotseatActivityTracker());
- startActivity(homeIntent);
- finish();
- }
-
- static class HotseatActivityTracker<T extends QuickstepLauncher> implements
- ActivityTracker.SchedulerCallback {
-
- @Override
- public boolean init(BaseActivity activity, boolean alreadyOnHome) {
- QuickstepLauncher launcher = (QuickstepLauncher) activity;
- if (launcher != null) {
- launcher.getHotseatPredictionController().showEdu();
- }
- return false;
- }
-
- }
-}
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index b40a1d5..13baf56 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -200,6 +200,7 @@
}
int predictionIndex = 0;
+ int numViewsAnimated = 0;
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
// make sure predicted icon removal and filling predictions don't step on each other
if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
@@ -233,7 +234,11 @@
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
- icon.applyFromWorkspaceItem(predictedItem);
+ boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
+ icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
+ if (animateIconChange) {
+ numViewsAnimated++;
+ }
icon.finishBinding(mPredictionLongClickListener);
} else {
newItems.add(predictedItem);
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index a9c2a5e..5769f0b 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -44,7 +44,6 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
@@ -68,7 +67,7 @@
/**
* Model delegate which loads prediction items
*/
-public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
+public class QuickstepModelDelegate extends ModelDelegate {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
@@ -93,7 +92,6 @@
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
- mIDP.addOnChangeListener(this);
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
}
@@ -179,7 +177,6 @@
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
destroyPredictors();
- mIDP.removeOnChangeListener(this);
}
private void destroyPredictors() {
@@ -250,12 +247,6 @@
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
}
- @Override
- public void onIdpChanged(InvariantDeviceProfile profile) {
- // Reinitialize everything
- Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
- }
-
private void onAppTargetEvent(AppTargetEvent event, int client) {
PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState;
if (state.predictor != null) {
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index aba16c2..3e2fb63 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -26,6 +26,7 @@
import android.os.IBinder;
import android.os.SystemProperties;
import android.util.FloatProperty;
+import android.view.AttachedSurfaceControl;
import android.view.CrossWindowBlurListeners;
import android.view.SurfaceControl;
import android.view.View;
@@ -95,7 +96,11 @@
public void onDraw() {
View view = mLauncher.getDragLayer();
ViewRootImpl viewRootImpl = view.getViewRootImpl();
- setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
+ boolean applied = setSurface(
+ viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
+ if (!applied) {
+ dispatchTransactionSurface(mDepth);
+ }
view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
}
};
@@ -108,6 +113,13 @@
}
};
+ private final Runnable mOpaquenessListener = new Runnable() {
+ @Override
+ public void run() {
+ dispatchTransactionSurface(mDepth);
+ }
+ };
+
private final Launcher mLauncher;
/**
* Blur radius when completely zoomed out, in pixels.
@@ -158,23 +170,28 @@
if (windowToken != null) {
mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
}
- CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
- mCrossWindowBlurListener);
+ onAttached();
}
@Override
public void onViewDetachedFromWindow(View view) {
CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
+ mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
}
};
mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
if (mLauncher.getRootView().isAttachedToWindow()) {
- CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
- mCrossWindowBlurListener);
+ onAttached();
}
}
}
+ private void onAttached() {
+ CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
+ mCrossWindowBlurListener);
+ mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
+ }
+
/**
* Sets if the underlying activity is started or not
*/
@@ -189,20 +206,22 @@
/**
* Sets the specified app target surface to apply the blur to.
+ * @return true when surface was valid and transaction was dispatched.
*/
- public void setSurface(SurfaceControl surface) {
+ public boolean setSurface(SurfaceControl surface) {
// Set launcher as the SurfaceControl when we don't need an external target anymore.
if (surface == null) {
ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null;
}
-
if (mSurface != surface) {
mSurface = surface;
if (surface != null) {
dispatchTransactionSurface(mDepth);
+ return true;
}
}
+ return false;
}
@Override
@@ -216,6 +235,8 @@
setDepth(toDepth);
} else if (toState == LauncherState.OVERVIEW) {
dispatchTransactionSurface(mDepth);
+ } else if (toState == LauncherState.BACKGROUND_APP) {
+ mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
}
}
@@ -262,7 +283,7 @@
public void onOverlayScrollChanged(float progress) {
// Round out the progress to dedupe frequent, non-perceptable updates
int progressI = (int) (progress * 256);
- float progressF = progressI / 256f;
+ float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f);
if (Float.compare(mOverlayScrollProgress, progressF) == 0) {
return;
}
@@ -283,13 +304,10 @@
}
if (supportsBlur) {
- // We cannot mark the window as opaque in overview because there will be an app window
- // below the launcher layer, and we need to draw it -- without blurs.
- boolean isOverview = mLauncher.isInState(LauncherState.OVERVIEW);
- boolean opaque = mLauncher.getScrimView().isFullyOpaque() && !isOverview;
+ boolean opaque = mLauncher.getScrimView().isFullyOpaque();
- int blur = opaque || isOverview || !mCrossWindowBlursEnabled
- || mBlurDisabledForAppLaunch ? 0 : (int) (depth * mMaxBlurRadius);
+ int blur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
+ ? 0 : (int) (depth * mMaxBlurRadius);
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, blur)
.setOpaque(mSurface, opaque);
@@ -304,7 +322,12 @@
transaction.setEarlyWakeupEnd();
mInEarlyWakeUp = false;
}
- transaction.apply();
+
+ AttachedSurfaceControl rootSurfaceControl =
+ mLauncher.getRootView().getRootSurfaceControl();
+ if (rootSurfaceControl != null) {
+ rootSurfaceControl.applyTransactionOnDraw(transaction);
+ }
}
return true;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index b5c834d..764b0d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
import android.animation.Animator;
@@ -23,23 +24,32 @@
import android.animation.ObjectAnimator;
import android.graphics.Rect;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.RecentsAnimationController;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import java.util.Arrays;
+import java.util.stream.Stream;
/**
* A data source which integrates with a Launcher instance
@@ -58,9 +68,13 @@
private final AnimatedFloat mIconAlignmentForGestureState =
new AnimatedFloat(this::onIconAlignmentRatioChanged);
+ private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
+ this::onStashedInAppChanged;
+
// Initialized in init.
private TaskbarControllers mControllers;
private AnimatedFloat mTaskbarBackgroundAlpha;
+ private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
private AlphaProperty mIconAlphaForHome;
private boolean mIsAnimatingToLauncherViaResume;
private boolean mIsAnimatingToLauncherViaGesture;
@@ -84,6 +98,8 @@
mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
.getTaskbarBackgroundAlpha();
+ mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
+ .getOverrideBackgroundAlpha();
MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
@@ -94,6 +110,9 @@
onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
mIconAlignmentForResumedState.finishAnimation();
onIconAlignmentRatioChanged();
+
+ onStashedInAppChanged(mLauncher.getDeviceProfile());
+ mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
}
@Override
@@ -102,6 +121,7 @@
mIconAlignmentForResumedState.finishAnimation();
mIconAlignmentForGestureState.finishAnimation();
+ mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
mLauncher.getHotseat().setIconsAlpha(1f);
mLauncher.setTaskbarUIController(null);
}
@@ -144,10 +164,9 @@
anim.start();
mIsAnimatingToLauncherViaResume = isResumed;
- if (!isResumed) {
- TaskbarStashController stashController = mControllers.taskbarStashController;
- stashController.animateToIsStashed(stashController.isStashedInApp(), duration);
- }
+ TaskbarStashController stashController = mControllers.taskbarStashController;
+ stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
+ stashController.applyState(duration);
SystemUiProxy.INSTANCE.get(mContext).notifyTaskbarStatus(!isResumed,
mControllers.taskbarStashController.isStashedInApp());
}
@@ -159,8 +178,7 @@
* automatically reset once the recents animation finishes
*/
public Animator createAnimToLauncher(@NonNull LauncherState toState,
- @NonNull RecentsAnimationCallbacks callbacks,
- long duration) {
+ @NonNull RecentsAnimationCallbacks callbacks, long duration) {
TaskbarStashController stashController = mControllers.taskbarStashController;
ObjectAnimator animator = mIconAlignmentForGestureState
.animateToValue(1)
@@ -176,40 +194,27 @@
public void onAnimationStart(Animator animation) {
mTargetStateOverride = toState;
mIsAnimatingToLauncherViaGesture = true;
- // TODO: base this on launcher state
- stashController.animateToIsStashed(false, duration);
+ // TODO: FLAG_IN_APP might be sufficient for now, but in the future we do want to
+ // add another flag for LauncherState as well. We will need to decide whether to
+ // show hotseat or the task bar.
+ stashController.updateStateForFlag(FLAG_IN_APP, false);
+ stashController.applyState(duration);
}
});
- callbacks.addListener(new RecentsAnimationListener() {
- @Override
- public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
- endGestureStateOverride(true);
- }
- @Override
- public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- endGestureStateOverride(!controller.getFinishTargetIsLauncher());
- }
-
- private void endGestureStateOverride(boolean finishedToApp) {
- callbacks.removeListener(this);
- mIsAnimatingToLauncherViaGesture = false;
-
- mIconAlignmentForGestureState
- .animateToValue(0)
- .start();
-
- if (finishedToApp) {
- // We only need this for the exiting live tile case.
- stashController.animateToIsStashed(stashController.isStashedInApp());
- }
- }
+ TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks);
+ callbacks.addListener(listener);
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.setTaskLaunchListener(() -> {
+ listener.endGestureStateOverride(true);
+ callbacks.removeListener(listener);
});
+
return animator;
}
private float getCurrentIconAlignmentRatio() {
- return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+ return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
}
private void onIconAlignmentRatioChanged() {
@@ -245,8 +250,90 @@
return mContext.getDragController().isDragging();
}
- void setTaskbarViewVisible(boolean isVisible) {
+ public View getRootView() {
+ return mTaskbarDragLayer;
+ }
+
+ private void setTaskbarViewVisible(boolean isVisible) {
mIconAlphaForHome.setValue(isVisible ? 1 : 0);
mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
}
+
+ @Override
+ protected void onStashedInAppChanged() {
+ onStashedInAppChanged(mLauncher.getDeviceProfile());
+ }
+
+ private void onStashedInAppChanged(DeviceProfile deviceProfile) {
+ boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
+ deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
+ }
+
+ /**
+ * Sets whether the background behind the taskbar/nav bar should be hidden.
+ */
+ public void forceHideBackground(boolean forceHide) {
+ mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
+ }
+
+ @Override
+ public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
+ return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
+ }
+
+ /**
+ * Starts the taskbar education flow, if the user hasn't seen it yet.
+ */
+ public void showEdu() {
+ if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()
+ || Utilities.IS_RUNNING_IN_TEST_HARNESS
+ || mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN)) {
+ return;
+ }
+ mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
+
+ mControllers.taskbarEduController.showEdu();
+ }
+
+ /**
+ * Manually ends the taskbar education flow.
+ */
+ public void hideEdu() {
+ if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()) {
+ return;
+ }
+
+ mControllers.taskbarEduController.hideEdu();
+ }
+
+ private final class TaskBarRecentsAnimationListener implements RecentsAnimationListener {
+ private final RecentsAnimationCallbacks mCallbacks;
+
+ TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ endGestureStateOverride(true);
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+ }
+
+ private void endGestureStateOverride(boolean finishedToApp) {
+ mCallbacks.removeListener(this);
+ mIsAnimatingToLauncherViaGesture = false;
+
+ mIconAlignmentForGestureState
+ .animateToValue(0)
+ .start();
+
+ TaskbarStashController controller = mControllers.taskbarStashController;
+ controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
+ controller.applyState();
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 08300e2..79bea03 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -25,16 +25,21 @@
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
+import android.annotation.LayoutRes;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.provider.Settings;
import android.util.Property;
import android.view.View;
import android.view.View.OnClickListener;
@@ -51,6 +56,7 @@
import com.android.launcher3.taskbar.contextual.RotationButton;
import com.android.launcher3.taskbar.contextual.RotationButtonController;
import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.AnimatedFloat;
import java.util.ArrayList;
@@ -69,6 +75,9 @@
private static final int FLAG_A11Y_VISIBLE = 1 << 3;
private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
+ private static final int FLAG_DISABLE_HOME = 1 << 6;
+ private static final int FLAG_DISABLE_RECENTS = 1 << 7;
+ private static final int FLAG_DISABLE_BACK = 1 << 8;
private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
@@ -115,21 +124,16 @@
.getProperty(ALPHA_INDEX_IME),
flags -> (flags & FLAG_IME_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
+ boolean isThreeButtonNav = mContext.isThreeButtonNav();
// IME switcher
View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
- mEndContextualContainer, mControllers.navButtonController, R.id.ime_switcher);
+ isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
+ mControllers.navButtonController, R.id.ime_switcher);
mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
&& ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)
&& ((flags & FLAG_A11Y_VISIBLE) == 0)));
- View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
- mStartContextualContainer, mControllers.navButtonController, R.id.back);
- imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
- // Rotate when Ime visible
- mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
- flags -> (flags & FLAG_IME_VISIBLE) != 0));
-
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarViewController.getTaskbarIconAlpha()
.getProperty(ALPHA_INDEX_KEYGUARD),
@@ -139,7 +143,10 @@
.getKeyguardBgTaskbar(),
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
- if (mContext.isThreeButtonNav()) {
+ // Force nav buttons (specifically back button) to be visible during setup wizard.
+ boolean areButtonsForcedVisible = !SettingsCache.INSTANCE.get(mContext).getValue(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
+ if (isThreeButtonNav || areButtonsForcedVisible) {
initButtons(mNavButtonContainer, mEndContextualContainer,
mControllers.navButtonController);
@@ -152,11 +159,18 @@
// Rotation button
RotationButton rotationButton = new RotationButtonImpl(
- addButton(mEndContextualContainer, R.id.rotate_suggestion));
+ addButton(mEndContextualContainer, R.id.rotate_suggestion,
+ R.layout.taskbar_contextual_button));
rotationButton.hide();
mControllers.rotationButtonController.setRotationButton(rotationButton);
} else {
mControllers.rotationButtonController.setRotationButton(new RotationButton() {});
+ View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+ mStartContextualContainer, mControllers.navButtonController, R.id.back);
+ imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
+ // Rotate when Ime visible
+ mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
+ flags -> (flags & FLAG_IME_VISIBLE) != 0));
}
applyState();
@@ -169,7 +183,11 @@
mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
mNavButtonContainer, mControllers.navButtonController, R.id.back);
mPropertyHolders.add(new StatePropertyHolder(mBackButton,
- flags -> (flags & FLAG_IME_VISIBLE) == 0));
+ flags -> (flags & FLAG_DISABLE_BACK) == 0));
+ boolean isRtl = Utilities.isRtl(mContext.getResources());
+ mPropertyHolders.add(new StatePropertyHolder(
+ mBackButton, flags -> (flags & FLAG_IME_VISIBLE) != 0, View.ROTATION,
+ isRtl ? 90 : -90, 0));
// Hide when keyguard is showing, show when bouncer is showing
mPropertyHolders.add(new StatePropertyHolder(mBackButton,
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
@@ -179,17 +197,18 @@
View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
navButtonController, R.id.home);
mPropertyHolders.add(new StatePropertyHolder(homeButton,
- flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
- (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
+ (flags & FLAG_DISABLE_HOME) == 0));
View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
navContainer, navButtonController, R.id.recent_apps);
mPropertyHolders.add(new StatePropertyHolder(recentsButton,
- flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
- (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
+ (flags & FLAG_DISABLE_RECENTS) == 0));
// A11y button
mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
- endContainer, navButtonController, R.id.accessibility_button);
+ endContainer, navButtonController, R.id.accessibility_button,
+ R.layout.taskbar_contextual_button);
mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
flags -> (flags & FLAG_A11Y_VISIBLE) != 0
&& (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
@@ -202,6 +221,12 @@
boolean a11yVisible = (systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean a11yLongClickable =
(systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+ boolean isHomeDisabled =
+ (systemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+ boolean isRecentsDisabled =
+ (systemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+ boolean isBackDisabled =
+ (systemUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
if (!forceUpdate && systemUiStateFlags == mSysuiStateFlags) {
return;
@@ -211,6 +236,10 @@
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing);
updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
+ updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
+ updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
+ updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
+
if (mA11yButton != null) {
// Only used in 3 button
mA11yButton.setLongClickable(a11yLongClickable);
@@ -242,6 +271,13 @@
}
/**
+ * Returns true if the recents (overview) button is disabled
+ */
+ public boolean isRecentsDisabled() {
+ return (mState & FLAG_DISABLE_RECENTS) != 0;
+ }
+
+ /**
* Adds the bounds corresponding to all visible buttons to provided region
*/
public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) {
@@ -275,15 +311,22 @@
private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
- ImageView buttonView = addButton(parent, id);
+ return addButton(drawableId, buttonType, parent, navButtonController, id,
+ R.layout.taskbar_nav_button);
+ }
+
+ private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
+ ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
+ @LayoutRes int layoutId) {
+ ImageView buttonView = addButton(parent, id, layoutId);
buttonView.setImageResource(drawableId);
buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
return buttonView;
}
- private ImageView addButton(ViewGroup parent, int id) {
+ private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
ImageView buttonView = (ImageView) mContext.getLayoutInflater()
- .inflate(R.layout.taskbar_nav_button, parent, false);
+ .inflate(layoutId, parent, false);
buttonView.setId(id);
parent.addView(buttonView);
mAllButtons.add(buttonView);
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
new file mode 100644
index 0000000..0224bc4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.R;
+
+public class StashedHandleView extends View {
+
+ private final @ColorInt int mStashedHandleLightColor;
+ private final @ColorInt int mStashedHandleDarkColor;
+ private final Rect mSampledRegion = new Rect();
+ private final int[] mTmpArr = new int[2];
+
+ public StashedHandleView(Context context) {
+ this(context, null);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mStashedHandleLightColor = ContextCompat.getColor(context,
+ R.color.taskbar_stashed_handle_light_color);
+ mStashedHandleDarkColor = ContextCompat.getColor(context,
+ R.color.taskbar_stashed_handle_dark_color);
+ }
+
+ public void updateSampledRegion() {
+ getLocationOnScreen(mTmpArr);
+ mSampledRegion.set(mTmpArr[0], mTmpArr[1], mTmpArr[0] + getWidth(),
+ mTmpArr[1] + getHeight());
+ }
+
+ public Rect getSampledRegion() {
+ return mSampledRegion;
+ }
+
+ public void updateHandleColor(boolean isRegionDark) {
+ setBackgroundColor(isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index df37261..2858d7c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import android.animation.Animator;
+import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -25,19 +26,30 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.util.Executors;
import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
/**
* Handles properties/data collection, then passes the results to our stashed handle View to render.
*/
public class StashedHandleViewController {
+ /**
+ * The SharedPreferences key for whether the stashed handle region is dark.
+ */
+ private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY =
+ "stashed_handle_region_is_dark";
+
private final TaskbarActivityContext mActivity;
- private final View mStashedHandleView;
+ private final SharedPreferences mPrefs;
+ private final StashedHandleView mStashedHandleView;
private final int mStashedHandleWidth;
private final int mStashedHandleHeight;
+ private final RegionSamplingHelper mRegionSamplingHelper;
private final AnimatedFloat mTaskbarStashedHandleAlpha = new AnimatedFloat(
this::updateStashedHandleAlpha);
private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
@@ -52,13 +64,31 @@
private boolean mIsAtStashedRevealBounds = true;
- public StashedHandleViewController(TaskbarActivityContext activity, View stashedHandleView) {
+ public StashedHandleViewController(TaskbarActivityContext activity,
+ StashedHandleView stashedHandleView) {
mActivity = activity;
+ mPrefs = Utilities.getPrefs(mActivity);
mStashedHandleView = stashedHandleView;
+ mStashedHandleView.updateHandleColor(
+ mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false));
final Resources resources = mActivity.getResources();
mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
mStashedHandleHeight = resources.getDimensionPixelSize(
R.dimen.taskbar_stashed_handle_height);
+ mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
+ new RegionSamplingHelper.SamplingCallback() {
+ @Override
+ public void onRegionDarknessChanged(boolean isRegionDark) {
+ mStashedHandleView.updateHandleColor(isRegionDark);
+ mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
+ isRegionDark).apply();
+ }
+
+ @Override
+ public Rect getSampledRegion(View sampledView) {
+ return mStashedHandleView.getSampledRegion();
+ }
+ }, Executors.UI_HELPER_EXECUTOR);
}
public void init(TaskbarControllers controllers) {
@@ -93,6 +123,10 @@
});
}
+ public void onDestroy() {
+ mRegionSamplingHelper.stopAndDestroy();
+ }
+
public AnimatedFloat getStashedHandleAlpha() {
return mTaskbarStashedHandleAlpha;
}
@@ -117,6 +151,16 @@
return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
}
+ public void onIsStashed(boolean isStashed) {
+ mRegionSamplingHelper.setWindowVisible(isStashed);
+ if (isStashed) {
+ mStashedHandleView.updateSampledRegion();
+ mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
+ } else {
+ mRegionSamplingHelper.stop();
+ }
+ }
+
protected void updateStashedHandleAlpha() {
mStashedHandleView.setAlpha(mTaskbarStashedHandleAlpha.value);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index dbe528f..0d684a0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Process;
@@ -36,12 +37,14 @@
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.RoundedCorner;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
@@ -60,6 +63,7 @@
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -83,6 +87,7 @@
private final TaskbarControllers mControllers;
private final WindowManager mWindowManager;
+ private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
private WindowManager.LayoutParams mWindowLayoutParams;
private boolean mIsFullscreen;
// The size we should return to when we call setTaskbarWindowFullscreen(false)
@@ -95,7 +100,8 @@
private boolean mIsDestroyed = false;
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
- TaskbarNavButtonController buttonController) {
+ TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
+ unfoldTransitionProgressProvider) {
super(windowContext, Themes.getActivityThemeRes(windowContext));
mDeviceProfile = dp;
@@ -115,7 +121,15 @@
R.layout.taskbar, null, false);
TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
- View stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
+ StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
+
+ Display display = windowContext.getDisplay();
+ Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
+ ? windowContext.getApplicationContext()
+ : windowContext.getApplicationContext().createDisplayContext(display);
+ mWindowManager = c.getSystemService(WindowManager.class);
+ mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+ mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
// Construct controllers.
mControllers = new TaskbarControllers(this,
@@ -126,19 +140,16 @@
R.color.popup_color_primary_light),
new TaskbarDragLayerController(this, mDragLayer),
new TaskbarViewController(this, taskbarView),
+ new TaskbarUnfoldAnimationController(unfoldTransitionProgressProvider,
+ mWindowManager),
new TaskbarKeyguardController(this),
new StashedHandleViewController(this, stashedHandleView),
- new TaskbarStashController(this));
-
- Display display = windowContext.getDisplay();
- Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
- ? windowContext.getApplicationContext()
- : windowContext.getApplicationContext().createDisplayContext(display);
- mWindowManager = c.getSystemService(WindowManager.class);
+ new TaskbarStashController(this),
+ new TaskbarEduController(this));
}
public void init() {
- mLastRequestedNonFullscreenHeight = mDeviceProfile.taskbarSize;
+ mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
mWindowLayoutParams = new WindowManager.LayoutParams(
MATCH_PARENT,
mLastRequestedNonFullscreenHeight,
@@ -157,6 +168,10 @@
mWindowLayoutParams,
new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
);
+ // Adjust the frame by the rounded corners (ie. leaving just the bar as the inset) when
+ // the IME is showing
+ mWindowLayoutParams.providedInternalImeInsets = Insets.of(0,
+ getDefaultTaskbarWindowHeight() - mDeviceProfile.taskbarSize, 0, 0);
// Initialize controllers after all are constructed.
mControllers.init();
@@ -168,6 +183,14 @@
return mNavMode == Mode.THREE_BUTTONS;
}
+ public int getLeftCornerRadius() {
+ return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
+ }
+
+ public int getRightCornerRadius() {
+ return mRightCorner == null ? 0 : mRightCorner.getRadius();
+ }
+
@Override
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
@@ -198,6 +221,14 @@
return mViewCache;
}
+ @Override
+ public boolean supportsIme() {
+ // Currently we don't support IME because we have FLAG_NOT_FOCUSABLE. We can remove that
+ // flag when opening a floating view that needs IME (such as Folder), but then that means
+ // Taskbar will be below IME and thus users can't click the back button.
+ return false;
+ }
+
/**
* Sets a new data-source for this taskbar instance
*/
@@ -222,6 +253,8 @@
systemUiStateFlags, forceUpdate);
mControllers.taskbarViewController.setImeIsVisible(
mControllers.navbarButtonsViewController.isImeVisible());
+ mControllers.taskbarViewController.setRecentsButtonDisabled(
+ mControllers.navbarButtonsViewController.isRecentsDisabled());
mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags);
}
@@ -234,7 +267,6 @@
return;
}
mControllers.rotationButtonController.onDisable2FlagChanged(state2);
- mControllers.taskbarKeyguardController.disableNavbarElements(state1, state2);
}
public void onSystemBarAttributesChanged(int displayId, int behavior) {
@@ -249,8 +281,12 @@
setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenHeight);
}
+ public boolean isTaskbarWindowFullscreen() {
+ return mIsFullscreen;
+ }
+
/**
- * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
+ * Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset).
*/
public void setTaskbarWindowHeight(int height) {
if (mWindowLayoutParams.height == height || mIsDestroyed) {
@@ -270,6 +306,13 @@
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
+ /**
+ * Returns the default height of the window, including the static corner radii above taskbar.
+ */
+ public int getDefaultTaskbarWindowHeight() {
+ return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius());
+ }
+
protected void onTaskbarIconClicked(View view) {
Object tag = view.getTag();
if (tag instanceof Task) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index be26913..6144881 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -31,9 +31,11 @@
public final RotationButtonController rotationButtonController;
public final TaskbarDragLayerController taskbarDragLayerController;
public final TaskbarViewController taskbarViewController;
+ public final TaskbarUnfoldAnimationController taskbarUnfoldAnimationController;
public final TaskbarKeyguardController taskbarKeyguardController;
public final StashedHandleViewController stashedHandleViewController;
public final TaskbarStashController taskbarStashController;
+ public final TaskbarEduController taskbarEduController;
/** Do not store this controller, as it may change at runtime. */
@NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
@@ -45,9 +47,11 @@
RotationButtonController rotationButtonController,
TaskbarDragLayerController taskbarDragLayerController,
TaskbarViewController taskbarViewController,
+ TaskbarUnfoldAnimationController taskbarUnfoldAnimationController,
TaskbarKeyguardController taskbarKeyguardController,
StashedHandleViewController stashedHandleViewController,
- TaskbarStashController taskbarStashController) {
+ TaskbarStashController taskbarStashController,
+ TaskbarEduController taskbarEduController) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
this.navButtonController = navButtonController;
@@ -55,9 +59,11 @@
this.rotationButtonController = rotationButtonController;
this.taskbarDragLayerController = taskbarDragLayerController;
this.taskbarViewController = taskbarViewController;
+ this.taskbarUnfoldAnimationController = taskbarUnfoldAnimationController;
this.taskbarKeyguardController = taskbarKeyguardController;
this.stashedHandleViewController = stashedHandleViewController;
this.taskbarStashController = taskbarStashController;
+ this.taskbarEduController = taskbarEduController;
}
/**
@@ -72,9 +78,11 @@
}
taskbarDragLayerController.init(this);
taskbarViewController.init(this);
+ taskbarUnfoldAnimationController.init(this);
taskbarKeyguardController.init(navbarButtonsViewController);
stashedHandleViewController.init(this);
taskbarStashController.init(this);
+ taskbarEduController.init(this);
}
/**
@@ -85,6 +93,8 @@
rotationButtonController.onDestroy();
taskbarDragLayerController.onDestroy();
taskbarKeyguardController.onDestroy();
+ taskbarUnfoldAnimationController.onDestroy();
taskbarViewController.onDestroy();
+ stashedHandleViewController.onDestroy();
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index cd1baf7..b42a60c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -40,9 +41,12 @@
public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
private final Paint mTaskbarBackgroundPaint;
+ private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+ // Initialized in init.
private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
+ private float mLeftCornerRadius, mRightCornerRadius;
private float mTaskbarBackgroundOffset;
@@ -65,10 +69,32 @@
mTaskbarBackgroundPaint = new Paint();
mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
mTaskbarBackgroundPaint.setAlpha(0);
+ mTaskbarBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mTaskbarBackgroundPaint.setStyle(Paint.Style.FILL);
+
+ // Will be set in init(), but this ensures they are always non-null.
+ mInvertedLeftCornerPath = new Path();
+ mInvertedRightCornerPath = new Path();
}
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
mControllerCallbacks = callbacks;
+
+ // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
+ // square, and then subtracting out a circle from the appropriate corner.
+ mLeftCornerRadius = mActivity.getLeftCornerRadius();
+ mRightCornerRadius = mActivity.getRightCornerRadius();
+ Path square = new Path();
+ square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
+ Path circle = new Path();
+ circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
+ mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+ square.reset();
+ square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
+ circle.reset();
+ circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
+ mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
+
recreateControllers();
}
@@ -121,8 +147,20 @@
protected void dispatchDraw(Canvas canvas) {
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
* (1f - mTaskbarBackgroundOffset);
- canvas.drawRect(0, canvas.getHeight() - backgroundHeight, canvas.getWidth(),
- canvas.getHeight(), mTaskbarBackgroundPaint);
+ canvas.save();
+ canvas.translate(0, canvas.getHeight() - backgroundHeight);
+
+ // Draw the background behind taskbar content.
+ canvas.drawRect(0, 0, canvas.getWidth(), backgroundHeight, mTaskbarBackgroundPaint);
+
+ // Draw the inverted rounded corners above the taskbar.
+ canvas.translate(0, -mLeftCornerRadius);
+ canvas.drawPath(mInvertedLeftCornerPath, mTaskbarBackgroundPaint);
+ canvas.translate(0, mLeftCornerRadius);
+ canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
+ canvas.drawPath(mInvertedRightCornerPath, mTaskbarBackgroundPaint);
+
+ canvas.restore();
super.dispatchDraw(canvas);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index df89285..b7c5db2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -36,10 +37,14 @@
private final TaskbarActivityContext mActivity;
private final TaskbarDragLayer mTaskbarDragLayer;
private final int mFolderMargin;
+
// Alpha properties for taskbar background.
private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha);
private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
+ private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
+
// Translation property for taskbar background.
private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset);
@@ -58,6 +63,7 @@
mControllers = controllers;
mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
mKeyguardBgTaskbar.value = 1;
+ mBgOverride.value = 1;
}
public void onDestroy() {
@@ -86,13 +92,19 @@
return mKeyguardBgTaskbar;
}
+ public AnimatedFloat getOverrideBackgroundAlpha() {
+ return mBgOverride;
+ }
+
public AnimatedFloat getTaskbarBackgroundOffset() {
return mBgOffset;
}
private void updateBackgroundAlpha() {
+ final float bgNavbar = mBgNavbar.value;
+ final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value;
mTaskbarDragLayer.setTaskbarBackgroundAlpha(
- Math.max(mBgNavbar.value, mBgTaskbar.value * mKeyguardBgTaskbar.value)
+ mBgOverride.value * Math.max(bgNavbar, bgTaskbar)
);
}
@@ -111,23 +123,22 @@
*/
public void updateInsetsTouchability(InsetsInfo insetsInfo) {
insetsInfo.touchableRegion.setEmpty();
- if (mActivity.isThreeButtonNav()) {
- // Always have nav buttons be touchable
- mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
- mTaskbarDragLayer, insetsInfo.touchableRegion);
- }
+ // Always have nav buttons be touchable
+ mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
+ mTaskbarDragLayer, insetsInfo.touchableRegion);
if (mTaskbarDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
} else if (mControllers.navbarButtonsViewController.isImeVisible()) {
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_CONTENT);
} else if (!mControllers.uiController.isTaskbarTouchable()) {
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
} else if (mControllers.taskbarViewController.areIconsVisible()) {
// Buttons are visible, take over the full taskbar area
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ insetsInfo.setTouchableInsets(mActivity.isTaskbarWindowFullscreen()
+ ? TOUCHABLE_INSETS_FRAME : TOUCHABLE_INSETS_CONTENT);
} else {
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
new file mode 100644
index 0000000..fd5c2ea
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Handles the Taskbar Education flow. */
+public class TaskbarEduController {
+
+ private static final long WAVE_ANIM_DELAY = 250;
+ private static final long WAVE_ANIM_STAGGER = 50;
+ private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
+ private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
+ // The fraction of each icon's animation at which we reach the top point of the wave.
+ private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
+ // The fraction of each icon's animation at which we reach the bottom, before overshooting.
+ private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
+ private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
+ private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
+ private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
+ private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
+ private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
+ // How many icons to cycle through in the slot machine (+ the original icon at each end).
+ private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;
+
+ private final TaskbarActivityContext mActivity;
+ private final float mWaveAnimTranslationY;
+ private final float mWaveAnimTranslationYReturnOvershoot;
+
+ // Initialized in init.
+ TaskbarControllers mControllers;
+
+ private TaskbarEduView mTaskbarEduView;
+ private Animator mAnim;
+
+ public TaskbarEduController(TaskbarActivityContext activity) {
+ mActivity = activity;
+
+ final Resources resources = activity.getResources();
+ mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
+ mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
+ R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
+ void showEdu() {
+ mActivity.setTaskbarWindowFullscreen(true);
+ mActivity.getDragLayer().post(() -> {
+ mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate(
+ R.layout.taskbar_edu, mActivity.getDragLayer(), false);
+ mTaskbarEduView.init(new TaskbarEduCallbacks());
+ mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
+ mTaskbarEduView.show();
+ startAnim(createWaveAnim());
+ });
+ }
+
+ void hideEdu() {
+ if (mTaskbarEduView != null) {
+ mTaskbarEduView.close(true /* animate */);
+ }
+ }
+
+ /**
+ * Starts the given animation, ending the previous animation first if it's still playing.
+ */
+ private void startAnim(Animator anim) {
+ if (mAnim != null) {
+ mAnim.end();
+ }
+ mAnim = anim;
+ mAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnim = null;
+ }
+ });
+ mAnim.start();
+ }
+
+ /**
+ * Creates a staggered "wave" animation where each icon translates and scales up in succession.
+ */
+ private Animator createWaveAnim() {
+ AnimatorSet waveAnim = new AnimatorSet();
+ View[] icons = mControllers.taskbarViewController.getIconViews();
+ for (int i = 0; i < icons.length; i++) {
+ View icon = icons[i];
+ AnimatorSet iconAnim = new AnimatorSet();
+
+ Keyframe[] scaleKeyframes = new Keyframe[] {
+ Keyframe.ofFloat(0, 1f),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
+ Keyframe.ofFloat(1f, 1f)
+ };
+ scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
+ scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
+
+ Keyframe[] translationYKeyframes = new Keyframe[] {
+ Keyframe.ofFloat(0, 0f),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
+ // Half of the remaining fraction overshoots, then the other half returns to 0.
+ Keyframe.ofFloat(
+ WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
+ mWaveAnimTranslationYReturnOvershoot),
+ Keyframe.ofFloat(1f, 0f)
+ };
+ translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
+ translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
+ translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
+ translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);
+
+ iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
+ PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
+ .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
+ iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
+ PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
+ .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
+
+ if (icon instanceof PredictedAppIcon) {
+ // Play slot machine animation through random icons from AllAppsList.
+ PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
+ ItemInfo itemInfo = (ItemInfo) icon.getTag();
+ List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
+ .filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
+ .map(appInfo -> appInfo.bitmap)
+ .filter(bitmap -> !bitmap.isNullOrLowRes())
+ .collect(Collectors.toList());
+ // Pick n icons at random.
+ Collections.shuffle(iconsToAnimate);
+ if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
+ iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
+ }
+ Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
+ if (slotMachineAnim != null) {
+ iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
+ }
+ }
+
+ iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
+ waveAnim.play(iconAnim);
+ }
+ waveAnim.setStartDelay(WAVE_ANIM_DELAY);
+ return waveAnim;
+ }
+
+ /**
+ * Callbacks for {@link TaskbarEduView} to interact with its controller.
+ */
+ class TaskbarEduCallbacks {
+ void onPageChanged(int currentPage, int pageCount) {
+ if (currentPage == 0) {
+ mTaskbarEduView.updateStartButton(R.string.taskbar_edu_close,
+ v -> mTaskbarEduView.close(true /* animate */));
+ } else {
+ mTaskbarEduView.updateStartButton(R.string.taskbar_edu_previous,
+ v -> mTaskbarEduView.snapToPage(currentPage - 1));
+ }
+ if (currentPage == pageCount - 1) {
+ mTaskbarEduView.updateEndButton(R.string.taskbar_edu_done,
+ v -> mTaskbarEduView.close(true /* animate */));
+ } else {
+ mTaskbarEduView.updateEndButton(R.string.taskbar_edu_next,
+ v -> mTaskbarEduView.snapToPage(currentPage + 1));
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduPagedView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduPagedView.java
new file mode 100644
index 0000000..5efcc4d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduPagedView.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_EDUCATION_DIALOG;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.taskbar.TaskbarEduController.TaskbarEduCallbacks;
+import com.android.launcher3.views.ActivityContext;
+
+/** Horizontal carousel of tutorial screens for Taskbar Edu. */
+public class TaskbarEduPagedView extends PagedView<PageIndicatorDots> {
+
+ private TaskbarEduView mTaskbarEduView;
+ private TaskbarEduCallbacks mControllerCallbacks;
+
+ public TaskbarEduPagedView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ void setTaskbarEduView(TaskbarEduView taskbarEduView) {
+ mTaskbarEduView = taskbarEduView;
+ mPageIndicator = taskbarEduView.findViewById(R.id.content_page_indicator);
+ initParentViews(taskbarEduView);
+ }
+
+ void setControllerCallbacks(TaskbarEduCallbacks controllerCallbacks) {
+ mControllerCallbacks = controllerCallbacks;
+ mControllerCallbacks.onPageChanged(getCurrentPage(), getPageCount());
+ }
+
+ @Override
+ protected int getChildGap() {
+ return mTaskbarEduView.getPaddingLeft() + mTaskbarEduView.getPaddingRight();
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ if (mMaxScroll > 0) {
+ mPageIndicator.setScroll(l, mMaxScroll);
+ }
+ }
+
+ @Override
+ protected void notifyPageSwitchListener(int prevPage) {
+ super.notifyPageSwitchListener(prevPage);
+ mControllerCallbacks.onPageChanged(getCurrentPage(), getPageCount());
+ }
+
+ @Override
+ protected boolean canScroll(float absVScroll, float absHScroll) {
+ return AbstractFloatingView.getTopOpenViewWithType(
+ ActivityContext.lookupContext(getContext()),
+ TYPE_ALL & ~TYPE_TASKBAR_EDUCATION_DIALOG) == null;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
new file mode 100644
index 0000000..8525427
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
+import com.android.launcher3.views.AbstractSlideInView;
+
+/** Education view about the Taskbar. */
+public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
+ implements Insettable {
+
+ private static final int DEFAULT_OPEN_DURATION = 500;
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+
+ private final Rect mInsets = new Rect();
+
+ private Button mStartButton;
+ private Button mEndButton;
+ private TaskbarEduPagedView mPagedView;
+
+ public TaskbarEduView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public TaskbarEduView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ protected void init(TaskbarEduController.TaskbarEduCallbacks callbacks) {
+ if (mPagedView != null) {
+ mPagedView.setControllerCallbacks(callbacks);
+ }
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(animate, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_TASKBAR_EDUCATION_DIALOG) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mContent = findViewById(R.id.edu_view);
+ mStartButton = findViewById(R.id.edu_start_button);
+ mEndButton = findViewById(R.id.edu_end_button);
+ mPagedView = findViewById(R.id.content);
+ mPagedView.setTaskbarEduView(this);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ mContent.setPadding(mContent.getPaddingStart(),
+ mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
+ }
+
+ @Override
+ protected void attachToContainer() {
+ if (mColorScrim != null) {
+ getPopupContainer().addView(mColorScrim, 0);
+ }
+ getPopupContainer().addView(this, 1);
+ }
+
+ /** Show the Education flow. */
+ public void show() {
+ attachToContainer();
+ animateOpen();
+ }
+
+ @Override
+ protected Pair<View, String> getAccessibilityTarget() {
+ return Pair.create(mContent, mIsOpen ? getContext().getString(R.string.taskbar_edu_opened)
+ : getContext().getString(R.string.taskbar_edu_closed));
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return context.getResources().getColor(R.color.widgets_picker_scrim);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int width = r - l;
+ int height = b - t;
+
+ // Lay out the content as center bottom aligned.
+ int contentWidth = mContent.getMeasuredWidth();
+ int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
+ mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
+ contentLeft + contentWidth, height);
+
+ setTranslationShift(mTranslationShift);
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
+ mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
+ }
+
+ void snapToPage(int page) {
+ mPagedView.snapToPage(page);
+ }
+
+ void updateStartButton(int textResId, OnClickListener onClickListener) {
+ mStartButton.setText(textResId);
+ mStartButton.setOnClickListener(onClickListener);
+ }
+
+ void updateEndButton(int textResId, OnClickListener onClickListener) {
+ mEndButton.setText(textResId);
+ mEndButton.setOnClickListener(onClickListener);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
index a2039b6..834cd9c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
@@ -1,7 +1,10 @@
package com.android.launcher3.taskbar;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import android.app.KeyguardManager;
@@ -9,7 +12,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.view.View;
/**
* Controller for managing keyguard state for taskbar
@@ -17,10 +19,11 @@
public class TaskbarKeyguardController {
private static final int KEYGUARD_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING |
- SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING;
+ SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING |
+ SYSUI_STATE_OVERVIEW_DISABLED | SYSUI_STATE_HOME_DISABLED |
+ SYSUI_STATE_BACK_DISABLED;
private final TaskbarActivityContext mContext;
- private int mDisabledNavIcons;
private int mKeyguardSysuiFlags;
private boolean mBouncerShowing;
private NavbarButtonsViewController mNavbarButtonsViewController;
@@ -70,22 +73,13 @@
mIsScreenOff = false;
}
- public void disableNavbarElements(int state1, int state2) {
- if (mDisabledNavIcons == state1) {
- // no change
- return;
- }
- mDisabledNavIcons = state1;
- updateIconsForBouncer();
- }
-
/**
* Hides/shows taskbar when keyguard is up
*/
private void updateIconsForBouncer() {
- boolean disableBack = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_BACK) != 0;
- boolean disableRecent = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_RECENT) != 0;
- boolean disableHome = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_HOME) != 0;
+ boolean disableBack = (mKeyguardSysuiFlags & SYSUI_STATE_BACK_DISABLED) != 0;
+ boolean disableRecent = (mKeyguardSysuiFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+ boolean disableHome = (mKeyguardSysuiFlags & SYSUI_STATE_HOME_DISABLED) != 0;
boolean onlyBackEnabled = !disableBack && disableRecent && disableHome;
boolean showBackForBouncer = onlyBackEnabled &&
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 45eabed..453bf1c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -22,8 +22,13 @@
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
+import android.net.Uri;
+import android.provider.Settings;
import android.view.Display;
import androidx.annotation.NonNull;
@@ -35,10 +40,12 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
/**
* Class to manage taskbar lifecycle
@@ -46,10 +53,19 @@
public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
SysUINavigationMode.NavigationModeChangeListener {
+ private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
+ Settings.Secure.USER_SETUP_COMPLETE);
+
private final Context mContext;
private final DisplayController mDisplayController;
private final SysUINavigationMode mSysUINavigationMode;
private final TaskbarNavButtonController mNavButtonController;
+ private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
+ private final ComponentCallbacks mComponentCallbacks;
+
+ // The source for this provider is set when Launcher is available
+ private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
+ new ScopedUnfoldTransitionProgressProvider();
private TaskbarActivityContext mTaskbarActivityContext;
private BaseQuickstepLauncher mLauncher;
@@ -71,9 +87,29 @@
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null);
mNavButtonController = new TaskbarNavButtonController(service);
+ mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
+ mComponentCallbacks = new ComponentCallbacks() {
+ private Configuration mOldConfig = mContext.getResources().getConfiguration();
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if ((mOldConfig.diff(newConfig) & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
+ // Color has changed, recreate taskbar to reload background color & icons.
+ recreateTaskbar();
+ }
+ mOldConfig = newConfig;
+ }
+
+ @Override
+ public void onLowMemory() { }
+ };
mDisplayController.addChangeListener(this);
mSysUINavigationMode.addModeChangeListener(this);
+ SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
+ mUserSetupCompleteListener);
+ mContext.registerComponentCallbacks(mComponentCallbacks);
+
recreateTaskbar();
}
@@ -109,6 +145,9 @@
*/
public void setLauncher(@NonNull BaseQuickstepLauncher launcher) {
mLauncher = launcher;
+ mUnfoldProgressProvider.setSourceProvider(launcher
+ .getUnfoldTransitionProgressProvider());
+
if (mTaskbarActivityContext != null) {
mTaskbarActivityContext.setUIController(
new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
@@ -124,6 +163,7 @@
if (mTaskbarActivityContext != null) {
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
}
+ mUnfoldProgressProvider.setSourceProvider(null);
}
}
@@ -142,8 +182,8 @@
return;
}
- mTaskbarActivityContext = new TaskbarActivityContext(
- mContext, dp.copy(mContext), mNavButtonController);
+ mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp.copy(mContext),
+ mNavButtonController, mUnfoldProgressProvider);
mTaskbarActivityContext.init();
if (mLauncher != null) {
mTaskbarActivityContext.setUIController(
@@ -188,6 +228,9 @@
destroyExistingTaskbar();
mDisplayController.removeChangeListener(this);
mSysUINavigationMode.removeModeChangeListener(this);
+ SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
+ mUserSetupCompleteListener);
+ mContext.unregisterComponentCallbacks(mComponentCallbacks);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 0efec53..fc277cc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -31,12 +31,17 @@
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
+import java.util.function.IntPredicate;
+
/**
* Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
* create a cohesive animation between stashed/unstashed states.
*/
public class TaskbarStashController {
+ public static final int FLAG_IN_APP = 1 << 0;
+ public static final int FLAG_STASHED_IN_APP = 1 << 1;
+
/**
* How long to stash/unstash when manually invoked via long press.
*/
@@ -78,6 +83,9 @@
private final int mStashedHeight;
private final int mUnstashedHeight;
+ private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
+ flags -> (((flags & FLAG_IN_APP) != 0) && (flags & FLAG_STASHED_IN_APP) != 0));
+
// Initialized in init.
private TaskbarControllers mControllers;
// Taskbar background properties.
@@ -94,6 +102,7 @@
private boolean mIsStashedInApp;
/** Whether we are currently visually stashed (might change based on launcher state). */
private boolean mIsStashed = false;
+ private int mState;
private @Nullable AnimatorSet mAnimator;
@@ -124,6 +133,7 @@
mIsStashedInApp = supportsStashing()
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
+ updateStateForFlag(FLAG_STASHED_IN_APP, mIsStashedInApp);
SystemUiProxy.INSTANCE.get(mActivity)
.notifyTaskbarStatus(/* visible */ true, /* stashed */ mIsStashedInApp);
@@ -190,34 +200,19 @@
return false;
}
if (mIsStashedInApp != isStashedInApp) {
- boolean wasStashed = mIsStashedInApp;
mIsStashedInApp = isStashedInApp;
mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, mIsStashedInApp).apply();
- boolean isStashed = mIsStashedInApp;
- if (wasStashed != isStashed) {
- SystemUiProxy.INSTANCE.get(mActivity)
- .notifyTaskbarStatus(/* visible */ true, /* stashed */ isStashed);
- createAnimToIsStashed(isStashed, TASKBAR_STASH_DURATION).start();
- return true;
- }
+ updateStateForFlag(FLAG_STASHED_IN_APP, mIsStashedInApp);
+ applyState();
+
+ SystemUiProxy.INSTANCE.get(mActivity)
+ .notifyTaskbarStatus(/* visible */ true, /* stashed */ mIsStashedInApp);
+ mControllers.uiController.onStashedInAppChanged();
+ return true;
}
return false;
}
- /**
- * Starts an animation to the new stashed state with a default duration.
- */
- public void animateToIsStashed(boolean isStashed) {
- animateToIsStashed(isStashed, TASKBAR_STASH_DURATION);
- }
-
- /**
- * Starts an animation to the new stashed state with the specified duration.
- */
- public void animateToIsStashed(boolean isStashed, long duration) {
- createAnimToIsStashed(isStashed, duration).start();
- }
-
private Animator createAnimToIsStashed(boolean isStashed, long duration) {
AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
// Not exactly half and may overlap. See [first|second]HalfDurationScale below.
@@ -284,6 +279,7 @@
@Override
public void onAnimationStart(Animator animation) {
mIsStashed = isStashed;
+ onIsStashed(mIsStashed);
}
@Override
@@ -325,4 +321,51 @@
animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
}
+
+ private void onIsStashed(boolean isStashed) {
+ mControllers.stashedHandleViewController.onIsStashed(isStashed);
+ }
+
+ public void applyState() {
+ applyState(TASKBAR_STASH_DURATION);
+ }
+
+ public void applyState(long duration) {
+ mStatePropertyHolder.setState(mState, duration);
+ }
+
+ /**
+ * Updates the proper flag to indicate whether the task bar should be stashed.
+ *
+ * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
+ *
+ * @param flag The flag to update.
+ * @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
+ * unstashed.
+ */
+ public void updateStateForFlag(int flag, boolean enabled) {
+ if (enabled) {
+ mState |= flag;
+ } else {
+ mState &= ~flag;
+ }
+ }
+
+ private class StatePropertyHolder {
+ private final IntPredicate mStashCondition;
+
+ private boolean mIsStashed;
+
+ StatePropertyHolder(IntPredicate stashCondition) {
+ mStashCondition = stashCondition;
+ }
+
+ public void setState(int flags, long duration) {
+ boolean isStashed = mStashCondition.test(flags);
+ if (mIsStashed != isStashed) {
+ mIsStashed = isStashed;
+ createAnimToIsStashed(mIsStashed, duration).start();
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 260cedc..c0312a0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -17,6 +17,10 @@
import android.graphics.Rect;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+
+import java.util.stream.Stream;
+
/**
* Base class for providing different taskbar UI
*/
@@ -33,4 +37,10 @@
}
protected void updateContentInsets(Rect outContentInsets) { }
+
+ protected void onStashedInAppChanged() { }
+
+ public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
+ return Stream.empty();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
new file mode 100644
index 0000000..978bd47
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+/**
+ * Controls animation of taskbar icons when unfolding foldable devices
+ */
+public class TaskbarUnfoldAnimationController {
+
+ private final ScopedUnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+ private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimator;
+ private final TransitionListener mTransitionListener = new TransitionListener();
+ private TaskbarViewController mTaskbarViewController;
+
+ public TaskbarUnfoldAnimationController(ScopedUnfoldTransitionProgressProvider
+ unfoldTransitionProgressProvider, WindowManager windowManager) {
+ mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
+ mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
+ new LauncherViewsMoveFromCenterTranslationApplier());
+ }
+
+ /**
+ * Initializes the controller
+ * @param taskbarControllers references to all other taskbar controllers
+ */
+ public void init(TaskbarControllers taskbarControllers) {
+ mTaskbarViewController = taskbarControllers.taskbarViewController;
+ mTaskbarViewController.addOneTimePreDrawListener(() ->
+ mUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
+ mUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
+ }
+
+ /**
+ * Destroys the controller
+ */
+ public void onDestroy() {
+ mUnfoldTransitionProgressProvider.setReadyToHandleTransition(false);
+ mUnfoldTransitionProgressProvider.removeCallback(mTransitionListener);
+ }
+
+ private class TransitionListener implements TransitionProgressListener {
+
+ @Override
+ public void onTransitionStarted() {
+ mMoveFromCenterAnimator.updateDisplayProperties();
+ View[] icons = mTaskbarViewController.getIconViews();
+ for (View icon : icons) {
+ // TODO(b/193794563) we should re-register views if they are re-bound/re-inflated
+ // during the animation
+ mMoveFromCenterAnimator.registerViewForAnimation(icon);
+ }
+
+ mMoveFromCenterAnimator.onTransitionStarted();
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ mMoveFromCenterAnimator.onTransitionFinished();
+ mMoveFromCenterAnimator.clearRegisteredViews();
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ mMoveFromCenterAnimator.onTransitionProgress(progress);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index a4a92f7..59393d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -90,6 +90,9 @@
// We layout the icons to be of mIconTouchSize in width and height
mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
mItemPadding = (mIconTouchSize - actualIconSize) / 2;
+
+ // Needed to draw folder leave-behind when opening one.
+ setWillNotDraw(false);
}
protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
@@ -115,6 +118,7 @@
*/
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
int nextViewIndex = 0;
+ int numViewsAnimated = 0;
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
@@ -170,8 +174,14 @@
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
if (hotseatView instanceof BubbleTextView
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
- ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
- (WorkspaceItemInfo) hotseatItemInfo);
+ BubbleTextView btv = (BubbleTextView) hotseatView;
+ WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo;
+
+ boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
+ btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
+ if (animate) {
+ numViewsAnimated++;
+ }
}
setClickAndLongClickListenersForIcon(hotseatView);
nextViewIndex++;
@@ -233,7 +243,19 @@
if (!mTouchEnabled) {
return true;
}
- mControllerCallbacks.onTouchEvent(event);
+ if (mIconLayoutBounds.contains((int) event.getX(), (int) event.getY())) {
+ // Don't allow long pressing between icons.
+ return true;
+ }
+ if (mControllerCallbacks.onTouchEvent(event)) {
+ int oldAction = event.getAction();
+ try {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ return super.onTouchEvent(event);
+ } finally {
+ event.setAction(oldAction);
+ }
+ }
return super.onTouchEvent(event);
}
@@ -256,6 +278,18 @@
return mIconLayoutBounds;
}
+ /**
+ * Returns the app icons currently shown in the taskbar.
+ */
+ public View[] getIconViews() {
+ final int count = getChildCount();
+ View[] icons = new View[count];
+ for (int i = 0; i < count; i++) {
+ icons[i] = getChildAt(i);
+ }
+ return icons;
+ }
+
// FolderIconParent implemented methods.
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 6b95f08..f359a3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -16,20 +16,24 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AnimatedFloat.VALUE;
import android.graphics.Rect;
+import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.AnimatedFloat;
@@ -44,6 +48,8 @@
public static final int ALPHA_INDEX_IME = 1;
public static final int ALPHA_INDEX_KEYGUARD = 2;
public static final int ALPHA_INDEX_STASH = 3;
+ public static final int ALPHA_INDEX_RECENTS_DISABLED = 4;
+ private static final int NUM_ALPHA_CHANNELS = 5;
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
@@ -67,7 +73,7 @@
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTaskbarView = taskbarView;
- mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4);
+ mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
mTaskbarIconAlpha.setUpdateVisibility(true);
mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
}
@@ -101,16 +107,47 @@
}
/**
+ * Should be called when the recents button is disabled, so we can hide taskbar icons as well.
+ */
+ public void setRecentsButtonDisabled(boolean isDisabled) {
+ // TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
+ mTaskbarIconAlpha.getProperty(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
+ }
+
+ /**
* Sets OnClickListener and OnLongClickListener for the given view.
*/
public void setClickAndLongClickListenersForIcon(View icon) {
mTaskbarView.setClickAndLongClickListenersForIcon(icon);
}
+ /**
+ * Adds one time pre draw listener to the taskbar view, it is called before
+ * drawing a frame and invoked only once
+ * @param listener callback that will be invoked before drawing the next frame
+ */
+ public void addOneTimePreDrawListener(Runnable listener) {
+ mTaskbarView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ final ViewTreeObserver viewTreeObserver = mTaskbarView.getViewTreeObserver();
+ if (viewTreeObserver.isAlive()) {
+ listener.run();
+ viewTreeObserver.removeOnPreDrawListener(this);
+ }
+ return true;
+ }
+ });
+ }
+
public Rect getIconLayoutBounds() {
return mTaskbarView.getIconLayoutBounds();
}
+ public View[] getIconViews() {
+ return mTaskbarView.getIconViews();
+ }
+
public AnimatedFloat getTaskbarIconScaleForStash() {
return mTaskbarIconScaleForStash;
}
@@ -165,8 +202,9 @@
int offsetY = launcherDp.getTaskbarOffsetY();
setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
- int collapsedHeight = mActivity.getDeviceProfile().taskbarSize;
- int expandedHeight = collapsedHeight + offsetY;
+ int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
+ int expandedHeight = Math.max(collapsedHeight,
+ mActivity.getDeviceProfile().taskbarSize + offsetY);
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
@@ -179,7 +217,7 @@
float childCenter = (child.getLeft() + child.getRight()) / 2;
float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+ hotseatCellSize / 2;
- setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
+ setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
}
AnimatorPlaybackController controller = setter.createPlaybackController();
@@ -208,7 +246,11 @@
return view -> mControllers.taskbarStashController.updateAndAnimateIsStashedInApp(true);
}
- public void onTouchEvent(MotionEvent motionEvent) {
+ /**
+ * 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.
+ */
+ public boolean onTouchEvent(MotionEvent motionEvent) {
final float x = motionEvent.getRawX();
final float y = motionEvent.getRawY();
switch (motionEvent.getAction()) {
@@ -224,6 +266,7 @@
mControllers.taskbarStashController.startStashHint(
/* animateForward= */ false);
mCanceledStashHint = true;
+ return true;
}
break;
case MotionEvent.ACTION_UP:
@@ -234,6 +277,33 @@
}
break;
}
+ return false;
}
}
+
+ public static final FloatProperty<View> ICON_TRANSLATE_X =
+ new FloatProperty<View>("taskbarAligmentTranslateX") {
+
+ @Override
+ public void setValue(View view, float v) {
+ if (view instanceof BubbleTextView) {
+ ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
+ } else if (view instanceof FolderIcon) {
+ ((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
+ } else {
+ view.setTranslationX(v);
+ }
+ }
+
+ @Override
+ public Float get(View view) {
+ if (view instanceof BubbleTextView) {
+ return ((BubbleTextView) view)
+ .getTranslationXForTaskbarAlignmentAnimation();
+ } else if (view instanceof FolderIcon) {
+ return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
+ }
+ return view.getTranslationX();
+ }
+ };
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
index 99dc282..c776f16 100644
--- a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
@@ -149,7 +149,7 @@
public void init() {
registerListeners();
- if (mDisplayController.getInfo().id != DEFAULT_DISPLAY) {
+ if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) {
// Currently there is no accelerometer sensor on non-default display, disable fixed
// rotation for non-default display
onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
@@ -168,7 +168,7 @@
mListenersRegistered = true;
try {
WindowManagerGlobal.getWindowManagerService()
- .watchRotation(mRotationWatcher, mDisplayController.getInfo().id);
+ .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
} catch (IllegalArgumentException e) {
mListenersRegistered = false;
Log.w(TAG, "RegisterListeners for the display failed");
@@ -335,7 +335,7 @@
}
public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
- if (mDisplayController.getInfo().id != displayId) {
+ if (DEFAULT_DISPLAY != displayId) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 85943b6..c9909cc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -44,13 +44,26 @@
}
/**
+ * Returns a unique ID representing the display
+ */
+ public static String getUniqueId(Display display) {
+ return display.getUniqueId();
+ }
+
+ /**
* Returns the minimum space that should be left empty at the end of hotseat
*/
public static int getHotseatEndOffset(Context context) {
if (SysUINavigationMode.INSTANCE.get(context).getMode() == Mode.THREE_BUTTONS) {
Resources res = context.getResources();
+ /*
+ * 2 (left + right) x Padding +
+ * 3 nav buttons +
+ * Little space at the end for contextual buttons
+ */
return 2 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_spacing)
- + 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size);
+ + 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+ + res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
} else {
return 0;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index d839a36..ee6e8ce 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,6 +15,16 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -23,8 +33,10 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Process;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -35,6 +47,8 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
@@ -45,6 +59,10 @@
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* A BubbleTextView with a ring around it's drawable
*/
@@ -53,6 +71,9 @@
private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.095f;
+ private static final long ICON_CHANGE_ANIM_DURATION = 360;
+ private static final long ICON_CHANGE_ANIM_STAGGER = 50;
+
boolean mIsDrawingDot = false;
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -67,6 +88,25 @@
private int mPlateColor;
boolean mDrawForDrag = false;
+ // Used for the "slot-machine" education animation.
+ private List<Drawable> mSlotMachineIcons;
+ private Animator mSlotMachineAnim;
+ private float mSlotMachineIconTranslationY;
+
+ private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
+ new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
+ @Override
+ public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
+ predictedAppIcon.mSlotMachineIconTranslationY = transY;
+ predictedAppIcon.invalidate();
+ }
+
+ @Override
+ public Float get(PredictedAppIcon predictedAppIcon) {
+ return predictedAppIcon.mSlotMachineIconTranslationY;
+ }
+ };
+
public PredictedAppIcon(Context context) {
this(context, null, 0);
}
@@ -88,15 +128,38 @@
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
+ boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
if (!mIsPinned) {
drawEffect(canvas);
+ if (isSlotMachineAnimRunning) {
+ // Clip to to outside of the ring during the slot machine animation.
+ canvas.clipPath(mRingPath);
+ }
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
}
- super.onDraw(canvas);
+ if (isSlotMachineAnimRunning) {
+ drawSlotMachineIcons(canvas);
+ } else {
+ super.onDraw(canvas);
+ }
canvas.restoreToCount(count);
}
+ private void drawSlotMachineIcons(Canvas canvas) {
+ canvas.translate((getWidth() - getIconSize()) / 2f,
+ (getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
+ for (Drawable icon : mSlotMachineIcons) {
+ icon.setBounds(0, 0, getIconSize(), getIconSize());
+ icon.draw(canvas);
+ canvas.translate(0, getSlotMachineIconPlusSpacingSize());
+ }
+ }
+
+ private float getSlotMachineIconPlusSpacingSize() {
+ return getIconSize() + getOutlineOffsetY();
+ }
+
@Override
protected void drawDotIfNecessary(Canvas canvas) {
mIsDrawingDot = true;
@@ -109,9 +172,17 @@
}
@Override
- public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
- super.applyFromWorkspaceItem(info);
- mPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
+ // Create the slot machine animation first, since it uses the current icon to start.
+ Animator slotMachineAnim = animate
+ ? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
+ : null;
+ super.applyFromWorkspaceItem(info, animate, staggerIndex);
+ int oldPlateColor = mPlateColor;
+ int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ if (!animate) {
+ mPlateColor = newPlateColor;
+ }
if (mIsPinned) {
setContentDescription(info.contentDescription);
} else {
@@ -119,6 +190,76 @@
getContext().getString(R.string.hotseat_prediction_content_description,
info.contentDescription));
}
+
+ if (animate) {
+ ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
+ oldPlateColor, newPlateColor);
+ plateColorAnim.addUpdateListener(valueAnimator -> {
+ mPlateColor = (int) valueAnimator.getAnimatedValue();
+ invalidate();
+ });
+ AnimatorSet changeIconAnim = new AnimatorSet();
+ if (slotMachineAnim != null) {
+ changeIconAnim.play(slotMachineAnim);
+ }
+ changeIconAnim.play(plateColorAnim);
+ changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
+ changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
+ }
+ }
+
+ /**
+ * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
+ * and ending with the original icon.
+ */
+ public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
+ return createSlotMachineAnim(iconsToAnimate, true);
+ }
+
+ /**
+ * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
+ * with the original icon, then cycling through the given icons, optionally ending back with
+ * the original icon.
+ * @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
+ * than the last item in iconsToAnimate.
+ */
+ public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
+ boolean endWithOriginalIcon) {
+ if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
+ return null;
+ }
+ if (mSlotMachineAnim != null) {
+ mSlotMachineAnim.end();
+ }
+
+ // Bookend the other animating icons with the original icon on both ends.
+ mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
+ mSlotMachineIcons.add(getIcon());
+ iconsToAnimate.stream()
+ .map(iconInfo -> iconInfo.newThemedIcon(mContext))
+ .forEach(mSlotMachineIcons::add);
+ if (endWithOriginalIcon) {
+ mSlotMachineIcons.add(getIcon());
+ }
+
+ float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
+ Keyframe[] keyframes = new Keyframe[] {
+ Keyframe.ofFloat(0f, 0f),
+ 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);
+
+ mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
+ mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
+ mSlotMachineIcons = null;
+ mSlotMachineAnim = null;
+ mSlotMachineIconTranslationY = 0;
+ invalidate();
+ }));
+ return mSlotMachineAnim;
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index c2c721a..1cf50f7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -71,7 +71,7 @@
}
}
activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
Object itemInfo = hostView.getTag();
if (itemInfo instanceof ItemInfo) {
mLauncher.addLaunchCookie((ItemInfo) itemInfo, activityOptions.options);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index d822c8c..f8c9fd1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -70,7 +70,9 @@
@Override
protected float getDepthUnchecked(Context context) {
- return 1f;
+ // The scrim fades in at approximately 50% of the swipe gesture.
+ // This means that the depth should be greater than 1, in order to fully zoom out.
+ return 2f;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index fe5a347..4984b95 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -23,6 +23,7 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -89,6 +90,10 @@
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.isTaskbarPresentInApps) {
+ return launcher.getColor(R.color.taskbar_background);
+ }
return Color.TRANSPARENT;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 1882a0c..d0d7f31 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -43,8 +43,7 @@
@Override
public float getSplitSelectTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- int splitPosition = recentsView.getSplitPlaceholder()
- .getActiveSplitPositionOption().mStagePosition;
+ int splitPosition = recentsView.getSplitPlaceholder().getActiveSplitStagePosition();
if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) {
return 0f;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 283743d..ef6f53e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,7 +24,7 @@
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.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.ObjectAnimator;
@@ -38,11 +38,11 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 40c3e02..ff3c517 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -41,7 +41,7 @@
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -67,14 +67,15 @@
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
/**
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -398,6 +399,14 @@
nonOverviewAnim.setFloatValues(startProgress, endProgress);
mNonOverviewAnim.dispatchOnStart();
}
+ if (targetState == QUICK_SWITCH) {
+ // Navigating to quick switch, add scroll feedback since the first time is not
+ // considered a scroll by the RecentsView.
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE,
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
+ RecentsView.SCROLL_VIBRATION_FALLBACK);
+ }
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0603ba5..010f463 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -22,6 +22,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -41,6 +42,7 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -55,6 +57,12 @@
private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600;
+ public static final int TASK_DISMISS_VIBRATION_PRIMITIVE =
+ Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
+ public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
+ public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
+ VibratorWrapper.EFFECT_TEXTURE_TICK;
+
protected final T mActivity;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
@@ -334,10 +342,10 @@
fling = false;
}
PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
- boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
@@ -357,6 +365,10 @@
mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
mEndDisplacement, animationDuration);
+ if (goingUp && goingToEnd) {
+ VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
+ TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
+ }
}
private void clearState() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 42c89fd..1e841da 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -36,7 +36,6 @@
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;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -46,6 +45,7 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
@@ -65,6 +65,7 @@
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
@@ -87,18 +88,20 @@
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
@@ -106,7 +109,9 @@
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
+import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -222,7 +227,7 @@
protected final TaskAnimationManager mTaskAnimationManager;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
- private RunningWindowAnim mRunningWindowAnim;
+ private RunningWindowAnim[] mRunningWindowAnim;
// Possible second animation running at the same time as mRunningWindowAnim
private Animator mParallelRunningAnim;
private boolean mIsMotionPaused;
@@ -253,6 +258,10 @@
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
protected boolean mIsSwipingPipToHome;
+ // TODO(b/195473090) no split PIP for now, remove once we have more clarity
+ // can try to have RectFSpringAnim evaluate multiple rects at once
+ private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators =
+ new SwipePipToHomeAnimator[2];
// Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
private final float mQuickSwitchScaleScrollThreshold;
@@ -426,7 +435,8 @@
// RecentsView never updates the display rotation until swipe-up, force update
// RecentsOrientedState before passing to TaskViewSimulator.
mRecentsView.updateRecentsRotation();
- mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState());
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .setOrientationState(mRecentsView.getPagedViewOrientedState()));
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
@@ -519,7 +529,21 @@
}
protected void notifyGestureAnimationStartToRecents() {
- mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
+ ActivityManager.RunningTaskInfo[] runningTasks;
+ if (mIsSwipeForStagedSplit) {
+ int[] splitTaskIds =
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
+ runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
+ for (int i = 0; i < splitTaskIds.length; i++) {
+ int taskId = splitTaskIds[i];
+ ActivityManager.RunningTaskInfo rti = new ActivityManager.RunningTaskInfo();
+ rti.taskId = taskId;
+ runningTasks[i] = rti;
+ }
+ } else {
+ runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
+ }
+ mRecentsView.onGestureAnimationStart(runningTasks);
}
private void launcherFrameDrawn() {
@@ -606,15 +630,15 @@
if (animate) {
ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
reapplyWindowTransformAnim.addUpdateListener(anim -> {
- if (mRunningWindowAnim == null) {
- applyWindowTransform();
+ if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) {
+ applyScrollAndTransform();
}
});
reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
reapplyWindowTransformAnim::cancel);
} else {
- applyWindowTransform();
+ applyScrollAndTransform();
}
}
@@ -655,8 +679,13 @@
private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
mLauncherTransitionController = anim;
- mLauncherTransitionController.getNormalController().dispatchOnStart();
- updateLauncherTransitionProgress();
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
+ // Wait until the gesture is started (touch slop was passed) to start in sync with
+ // mWindowTransitionController. This ensures we don't hide the taskbar background when
+ // long pressing to stash it, for instance.
+ mLauncherTransitionController.getNormalController().dispatchOnStart();
+ updateLauncherTransitionProgress();
+ });
}
public Intent getLaunchIntent() {
@@ -678,7 +707,7 @@
}
updateSysUiFlags(mCurrentShift.value);
- applyWindowTransform();
+ applyScrollAndTransform();
updateLauncherTransitionProgress();
}
@@ -724,24 +753,25 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ super.onRecentsAnimationStart(controller, targets);
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+ mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
mRecentsAnimationController = controller;
mRecentsAnimationTargets = targets;
- mTransformParams.setTargetSet(mRecentsAnimationTargets);
- RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
- mGestureState.getRunningTaskId());
-
- if (runningTaskTarget != null) {
- mTaskViewSimulator.setPreview(runningTaskTarget);
- }
// Only initialize the device profile, if it has not been initialized before, as in some
// configurations targets.homeContentInsets may not be correct.
if (mActivity == null) {
- DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
- if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+ RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[0];
+ // orientation state is independent of which remote target handle we use since both
+ // should be pointing to the same one. Just choose index 0 for now since that works for
+ // both split and non-split
+ RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState();
+ DeviceProfile dp = orientationState.getLauncherDeviceProfile();
+ if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
Rect overviewStackBounds = mActivityInterface
- .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
dp = dp.getMultiWindowProfile(mContext,
new WindowBounds(overviewStackBounds, targets.homeContentInsets));
} else {
@@ -751,7 +781,7 @@
dp.updateInsets(targets.homeContentInsets);
dp.updateIsSeascape(mContext);
initTransitionEndpoints(dp);
- mTaskViewSimulator.getOrientationState().setMultiWindowMode(dp.isMultiWindowMode);
+ orientationState.setMultiWindowMode(dp.isMultiWindowMode);
}
// Notify when the animation starts
@@ -778,7 +808,9 @@
mActivityInitListener.unregister();
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
- TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
+ if (mRecentsAnimationTargets != null) {
+ TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
+ }
// Defer clearing the controller and the targets until after we've updated the state
mRecentsAnimationController = null;
@@ -851,6 +883,9 @@
*/
@UiThread
public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "3");
+ }
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
boolean isFling = mGestureStarted && !mIsMotionPaused
@@ -869,9 +904,17 @@
private void endRunningWindowAnim(boolean cancel) {
if (mRunningWindowAnim != null) {
if (cancel) {
- mRunningWindowAnim.cancel();
+ for (RunningWindowAnim r : mRunningWindowAnim) {
+ if (r != null) {
+ r.cancel();
+ }
+ }
} else {
- mRunningWindowAnim.end();
+ for (RunningWindowAnim r : mRunningWindowAnim) {
+ if (r != null) {
+ r.end();
+ }
+ }
}
}
if (mParallelRunningAnim != null) {
@@ -885,6 +928,9 @@
// Fast-finish the attaching animation if it's still running.
maybeUpdateRecentsAttachedState(false);
final GestureEndTarget endTarget = mGestureState.getEndTarget();
+ // Wait until the given View (if supplied) draws before resuming the last task.
+ View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
+
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
@@ -893,11 +939,13 @@
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
}
+
switch (endTarget) {
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify swipe-to-home (recents animation) is finished
SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
break;
case RECENTS:
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -907,7 +955,12 @@
mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
break;
case LAST_TASK:
- mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ if (postResumeLastTask != null) {
+ ViewUtils.postFrameDrawn(postResumeLastTask,
+ () -> mStateCallback.setState(STATE_RESUME_LAST_TASK));
+ } else {
+ mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ }
TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
break;
}
@@ -995,6 +1048,9 @@
@UiThread
private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
boolean isCancel) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "4");
+ }
long duration = MAX_SWIPE_DURATION;
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
@@ -1113,6 +1169,9 @@
@UiThread
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
GestureEndTarget target, PointF velocityPxPerMs) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "5");
+ }
runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
interpolator, target, velocityPxPerMs));
}
@@ -1163,6 +1222,9 @@
}
}
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "7, end target=" + mGestureState.getEndTarget());
+ }
if (mGestureState.getEndTarget() == HOME) {
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
@@ -1177,19 +1239,24 @@
&& runningTaskTarget.allowEnterPip
&& runningTaskTarget.taskInfo.pictureInPictureParams != null
&& runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "8, class=" + getClass().getSimpleName());
+ }
HomeAnimationFactory homeAnimFactory =
createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
runningTaskTarget);
mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
- final RectFSpringAnim windowAnim;
+ final RectFSpringAnim[] windowAnim;
if (mIsSwipingPipToHome) {
mSwipePipToHomeAnimator = createWindowAnimationToPip(
homeAnimFactory, runningTaskTarget, start);
- windowAnim = mSwipePipToHomeAnimator;
+ mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
+ windowAnim = mSwipePipToHomeAnimators;
} else {
mSwipePipToHomeAnimator = null;
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
- windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+
+ windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsAnimationController == null) {
@@ -1203,15 +1270,22 @@
}
});
}
- windowAnim.start(mContext, velocityPxPerMs);
- mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+ mRunningWindowAnim = new RunningWindowAnim[windowAnim.length];
+ for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) {
+ RectFSpringAnim windowAnimation = windowAnim[i];
+ if (windowAnimation == null) {
+ continue;
+ }
+ windowAnimation.start(mContext, velocityPxPerMs);
+ mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
+ }
homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y);
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
mLauncherTransitionController = null;
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
- mTaskViewSimulator);
+ getRemoteTaskViewSimulators());
}
} else {
AnimatorSet animatorSet = new AnimatorSet();
@@ -1253,11 +1327,12 @@
animatorSet.play(windowAnim);
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(
- animatorSet, mGestureState.getEndTarget(), mTaskViewSimulator);
+ animatorSet, mGestureState.getEndTarget(),
+ getRemoteTaskViewSimulators());
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
animatorSet.start();
- mRunningWindowAnim = RunningWindowAnim.wrap(animatorSet);
+ mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)};
}
}
@@ -1272,16 +1347,21 @@
}
}
+ /**
+ * TODO(b/195473090) handle multiple task simulators (if needed) for PIP
+ */
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
// Directly animate the app to PiP (picture-in-picture) mode
final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
- final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
+ final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState();
final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
final int homeRotation = orientationState.getRecentsActivityRotation();
final Matrix homeToWindowPositionMap = new Matrix();
- final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
+ final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap,
+ startProgress)[0];
// Move the startRect to Launcher space as floatingIconView runs in Launcher
final Matrix windowToHomePositionMap = new Matrix();
homeToWindowPositionMap.invert(windowToHomePositionMap);
@@ -1310,7 +1390,7 @@
// is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
if (homeRotation == ROTATION_0
&& (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
- builder.setFromRotation(mTaskViewSimulator, windowRotation,
+ builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
taskInfo.displayCutoutInsets);
}
final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
@@ -1340,7 +1420,7 @@
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
- setupWindowAnimation(swipePipToHomeAnimator);
+ setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator});
return swipePipToHomeAnimator;
}
@@ -1367,19 +1447,19 @@
* @param homeAnimationFactory The home animation factory.
*/
@Override
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
- RectFSpringAnim anim =
+ RectFSpringAnim[] anim =
super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
setupWindowAnimation(anim);
return anim;
}
- private void setupWindowAnimation(RectFSpringAnim anim) {
- anim.addOnUpdateListener((v, r, p) -> {
+ private void setupWindowAnimation(RectFSpringAnim[] anims) {
+ anims[0].addOnUpdateListener((v, r, p) -> {
updateSysUiFlags(Math.max(p, mCurrentShift.value));
});
- anim.addAnimatorListener(new AnimationSuccessListener() {
+ anims[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsView != null) {
@@ -1391,7 +1471,7 @@
}
});
if (mRecentsAnimationTargets != null) {
- mRecentsAnimationTargets.addReleaseCheck(anim);
+ mRecentsAnimationTargets.addReleaseCheck(anims[0]);
}
}
@@ -1639,7 +1719,7 @@
* if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
*/
private void maybeFinishSwipePipToHome() {
- if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
+ if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
mSwipePipToHomeAnimator.getComponentName(),
mSwipePipToHomeAnimator.getDestinationBounds(),
@@ -1680,8 +1760,8 @@
* depend on proper class initialization.
*/
protected void initAfterSubclassConstructor() {
- initTransitionEndpoints(
- mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
+ initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState().getLauncherDeviceProfile());
}
protected void performHapticFeedback() {
@@ -1698,7 +1778,8 @@
protected void linkRecentsViewScroll() {
SurfaceTransactionApplier.create(mRecentsView, applier -> {
- mTransformParams.setSyncTransactionApplier(applier);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
+ .setSyncTransactionApplier(applier));
runOnRecentsAnimationStart(() ->
mRecentsAnimationTargets.addReleaseCheck(applier));
});
@@ -1725,8 +1806,13 @@
mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
+ boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds().length > 0;
nextTask.launchTask(success -> {
resultCallback.accept(success);
+ if (isOldTaskSplit) {
+ SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(taskId);
+ }
if (success) {
if (hasTaskPreviouslyAppeared) {
onRestartPreviouslyAppearedTask();
@@ -1754,6 +1840,9 @@
* be run when it is next started.
*/
protected void runOnRecentsAnimationStart(Runnable action) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "6");
+ }
if (mRecentsAnimationTargets == null) {
mRecentsAnimationStartCallbacks.add(action);
} else {
@@ -1824,19 +1913,26 @@
/**
* Applies the transform on the recents animation
*/
- protected void applyWindowTransform() {
- if (mWindowTransitionController != null) {
- mWindowTransitionController.setProgress(
- Math.max(mCurrentShift.value, getScaleProgressDueToScroll()),
- mDragLengthFactor);
- }
+ protected void applyScrollAndTransform() {
// No need to apply any transform if there is ongoing swipe-pip-to-home animator since
// that animator handles the leash solely.
- if (mRecentsAnimationTargets != null && !mIsSwipingPipToHome) {
- if (mRecentsViewScrollLinked && mRecentsView != null) {
- mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
+ boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
+ for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
+ AnimatorControllerWithResistance playbackController =
+ remoteHandle.getPlaybackController();
+ if (playbackController != null) {
+ playbackController.setProgress(Math.max(mCurrentShift.value,
+ getScaleProgressDueToScroll()), mDragLengthFactor);
}
- mTaskViewSimulator.apply(mTransformParams);
+
+ if (notSwipingPipToHome) {
+ TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
+ if (setRecentsScroll) {
+ taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ }
+ taskViewSimulator.apply(remoteHandle.getTransformParams());
+ }
}
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
@@ -1891,7 +1987,6 @@
}
public interface Factory {
-
AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
}
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 624ade2..6298bb8 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -40,6 +40,7 @@
import android.os.Build;
import android.view.Gravity;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -203,8 +204,7 @@
/**
* Calculates the taskView size for the provided device configuration.
*/
- public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
- PagedOrientationHandler orientedState) {
+ public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
Resources res = context.getResources();
if (dp.overviewShowAsGrid) {
Rect gridRect = new Rect();
@@ -262,19 +262,35 @@
public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
if (dp.isMultiWindowMode) {
WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
- if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
- out.x = bounds.availableSize.x;
- out.y = bounds.availableSize.y;
- } else {
- out.x = bounds.availableSize.x + bounds.insets.left + bounds.insets.right;
- out.y = bounds.availableSize.y + bounds.insets.top + bounds.insets.bottom;
+ out.x = bounds.availableSize.x;
+ out.y = bounds.availableSize.y;
+ if (!TaskView.clipLeft(dp)) {
+ out.x += bounds.insets.left;
}
- } else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
- out.x = dp.availableWidthPx;
- out.y = dp.availableHeightPx;
+ if (!TaskView.clipRight(dp)) {
+ out.x += bounds.insets.right;
+ }
+ if (!TaskView.clipTop(dp)) {
+ out.y += bounds.insets.top;
+ }
+ if (!TaskView.clipBottom(dp)) {
+ out.y += bounds.insets.bottom;
+ }
} else {
out.x = dp.widthPx;
out.y = dp.heightPx;
+ if (TaskView.clipLeft(dp)) {
+ out.x -= dp.getInsets().left;
+ }
+ if (TaskView.clipRight(dp)) {
+ out.x -= dp.getInsets().right;
+ }
+ if (TaskView.clipTop(dp)) {
+ out.y -= dp.getInsets().top;
+ }
+ if (TaskView.clipBottom(dp)) {
+ out.y -= Math.max(dp.getInsets().bottom, dp.taskbarSize);
+ }
}
}
@@ -385,6 +401,15 @@
*/
public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
+ /**
+ * Called when the animation to the target has finished, but right before updating the state.
+ * @return A View that needs to draw before ending the recents animation to LAST_TASK.
+ * (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
+ */
+ public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
+ return null;
+ }
+
public interface AnimationFactory {
void createActivityInterface(long transitionLength);
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 7fb8e16..4df1aad 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -57,7 +57,7 @@
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
- calculateTaskSize(context, dp, outRect, orientationHandler);
+ calculateTaskSize(context, dp, outRect);
if (dp.isVerticalBarLayout()
&& SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index e2f198c..80ae65e 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -28,6 +28,7 @@
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -44,6 +45,7 @@
import android.os.Messenger;
import android.os.ParcelUuid;
import android.os.UserHandle;
+import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -56,6 +58,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.AppCloseConfig;
@@ -101,7 +104,9 @@
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
if (mRunningOverHome) {
- mTransformParams.setHomeBuilderProxy(this::updateHomeActivityTransformDuringSwipeUp);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp));
}
}
@@ -109,7 +114,9 @@
protected void initTransitionEndpoints(DeviceProfile dp) {
super.initTransitionEndpoints(dp);
if (mRunningOverHome) {
- mMaxLauncherScale = 1 / mTaskViewSimulator.getFullScreenScale();
+ // Full screen scale should be independent of remote target handle
+ mMaxLauncherScale = 1 / mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getFullScreenScale();
}
}
@@ -134,6 +141,10 @@
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
Intent intent = new Intent(mGestureState.getHomeIntent());
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME,
+ "createHomeAnimationFactory: " + intent.toShortString(true, true, true, false));
+ }
mActiveAnimationFactory.addGestureContract(intent);
try {
mContext.startActivity(intent, options.toBundle());
@@ -174,7 +185,8 @@
protected void notifyGestureAnimationStartToRecents() {
if (mRunningOverHome) {
if (SysUINavigationMode.getMode(mContext).hasGestures) {
- mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
+ mRecentsView.onGestureAnimationStartOnHome(
+ new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()});
}
} else {
super.notifyGestureAnimationStartToRecents();
@@ -202,19 +214,24 @@
mHomeAlpha = new AnimatedFloat();
mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
mVerticalShiftForScale.value = mCurrentShift.value;
- mTransformParams.setHomeBuilderProxy(
- this::updateHomeActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateHomeActivityTransformDuringHomeAnim));
} else {
mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
mHomeAlpha.value = 0;
-
- mHomeAlphaParams.setHomeBuilderProxy(
- this::updateHomeActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateHomeActivityTransformDuringHomeAnim));
}
mRecentsAlpha.value = 1;
- mTransformParams.setBaseBuilderProxy(
- this::updateRecentsActivityTransformDuringHomeAnim);
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateRecentsActivityTransformDuringHomeAnim));
}
@NonNull
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index d91d5b0..ae6ea79 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -25,10 +25,12 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -71,7 +73,7 @@
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
- calculateTaskSize(context, dp, outRect, orientationHandler);
+ calculateTaskSize(context, dp, outRect);
if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
} else {
@@ -131,6 +133,18 @@
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio, LINEAR);
+ pa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ LauncherTaskbarUIController taskbarUIController =
+ activity.getTaskbarUIController();
+ if (taskbarUIController != null) {
+ // Launcher's ScrimView will draw the background throughout the gesture.
+ taskbarUIController.forceHideBackground(true);
+ }
+ }
+ });
+
}
};
@@ -288,6 +302,10 @@
} else {
om.hideOverlay(150);
}
+ LauncherTaskbarUIController taskbarController = getTaskbarController();
+ if (taskbarController != null) {
+ taskbarController.hideEdu();
+ }
}
@Override
@@ -354,4 +372,16 @@
return NORMAL;
}
}
+
+ @Override
+ public View onSettledOnEndTarget(@Nullable GestureEndTarget endTarget) {
+ View superRet = super.onSettledOnEndTarget(endTarget);
+ LauncherTaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ // Start drawing taskbar's background again since launcher might stop drawing.
+ taskbarUIController.forceHideBackground(false);
+ return taskbarUIController.getRootView();
+ }
+ return superRet;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 19cad53..dc22a61 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -106,9 +106,11 @@
boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
mActivity.getRootView().setForceHideBackArrow(true);
- mActivity.setHintUserWillBeActive();
+ if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ mActivity.setHintUserWillBeActive();
+ }
- if (!canUseWorkspaceView || appCanEnterPip) {
+ if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) {
return new LauncherHomeAnimationFactory();
}
if (workspaceView instanceof LauncherAppWidgetHostView) {
@@ -179,14 +181,16 @@
final float floatingWidgetAlpha = isTargetTranslucent ? 0 : 1;
RectF backgroundLocation = new RectF();
Rect crop = new Rect();
- mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
+ // We can assume there is only one remote target here because staged split never animates
+ // into the app icon, only into the homescreen
+ mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().roundOut(crop);
Size windowSize = new Size(crop.width(), crop.height());
int fallbackBackgroundColor =
FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
hostView, backgroundLocation, windowSize,
- mTaskViewSimulator.getCurrentCornerRadius(), isTargetTranslucent,
- fallbackBackgroundColor);
+ mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCornerRadius(),
+ isTargetTranslucent, fallbackBackgroundColor);
return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 35a851a..81e6917 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -462,7 +462,7 @@
+ "mRotation: " + mRotation
+ " this: " + this);
}
- event.transform(mTmpMatrix);
+ event.applyTransform(mTmpMatrix);
return true;
}
mTmpPoint[0] = event.getX();
@@ -478,7 +478,7 @@
}
if (contains(mTmpPoint[0], mTmpPoint[1])) {
- event.transform(mTmpMatrix);
+ event.applyTransform(mTmpMatrix);
return true;
}
return false;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 8fb851c..b232464 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -32,6 +32,7 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -157,6 +158,7 @@
}
if (cmd.type == TYPE_HOME) {
mService.startActivity(mOverviewComponentObserver.getHomeIntent());
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
return true;
}
} else {
@@ -175,6 +177,7 @@
return launchTask(recents, getNextTask(recents), cmd);
case TYPE_HOME:
recents.startHome();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
return true;
}
}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 780032e..0efe666 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -33,11 +33,8 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.SystemClock;
-import android.util.Log;
import android.util.SparseIntArray;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.OverviewComponentObserverProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -132,16 +129,6 @@
private void updateOverviewTargets() {
ComponentName defaultHome = PackageManagerWrapper.getInstance()
.getHomeActivities(new ArrayList<>());
- if (TestProtocol.sDebugTracing && defaultHome == null) {
- Log.d(TestProtocol.THIRD_PARTY_LAUNCHER_NOT_SET, "getHomeActivities returned null");
- while ((defaultHome =
- PackageManagerWrapper.getInstance().getHomeActivities(new ArrayList<>()))
- == null) {
- SystemClock.sleep(10);
- }
- Log.d(TestProtocol.THIRD_PARTY_LAUNCHER_NOT_SET,
- "getHomeActivities returned non-null: " + defaultHome);
- }
mIsHomeDisabled = mDeviceState.isHomeDisabled();
mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 03e2395..cc6cfd7 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -122,7 +122,7 @@
SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+ new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this));
mDragLayer.recreateControllers();
mFallbackRecentsView.init(mActionsView, controller);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 239233b..3c05a3e 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -16,20 +16,24 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.graphics.Rect;
import android.util.ArraySet;
+import android.util.Log;
import android.view.RemoteAnimationTarget;
import androidx.annotation.BinderThread;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.util.Arrays;
import java.util.Set;
/**
@@ -93,8 +97,19 @@
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "RecentsAnimationCallbacks.onAnimationStart");
+ }
+ // Convert appTargets to type RemoteAnimationTarget for all apps except Home app
+ RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets)
+ .filter(remoteAnimationTarget ->
+ remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME)
+ .map(RemoteAnimationTargetCompat::unwrap)
+ .toArray(RemoteAnimationTarget[]::new);
+
RemoteAnimationTarget[] nonAppTargets =
- mSystemUiProxy.onGoingToRecentsLegacy(mCancelled);
+ mSystemUiProxy.onGoingToRecentsLegacy(mCancelled, nonHomeApps);
+
RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
homeContentInsets, minimizedHomeBounds);
@@ -106,6 +121,10 @@
mController::finishAnimationToApp);
} else {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME,
+ "RecentsAnimationCallbacks.onAnimationStart callback");
+ }
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationStart(mController, targets);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 444d77a..8a9bf7c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -18,6 +18,7 @@
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;
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -59,7 +60,6 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
@@ -67,7 +67,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
@@ -147,7 +146,7 @@
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
- mDisplayId = mDisplayController.getInfo().id;
+ mDisplayId = DEFAULT_DISPLAY;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index c032889..b20d488 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -77,8 +77,12 @@
* Gets the navigation bar remote animation target if exists.
*/
public RemoteAnimationTargetCompat getNavBarRemoteAnimationTarget() {
+ return getNonAppTargetOfType(TYPE_NAVIGATION_BAR);
+ }
+
+ public RemoteAnimationTargetCompat getNonAppTargetOfType(int type) {
for (RemoteAnimationTargetCompat target : nonApps) {
- if (target.windowType == TYPE_NAVIGATION_BAR) {
+ if (target.windowType == type) {
return target;
}
}
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
new file mode 100644
index 0000000..dc04016
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -0,0 +1,175 @@
+/*
+ * 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;
+
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.LauncherSplitScreenListener;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Glues together the necessary components to animate a remote target using a
+ * {@link TaskViewSimulator}
+ */
+public class RemoteTargetGluer {
+ private final RemoteTargetHandle[] mRemoteTargetHandles;
+ private SplitConfigurationOptions.StagedSplitBounds mStagedSplitBounds;
+
+ /**
+ * Use this constructor if remote targets are split-screen independent
+ */
+ public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy,
+ RemoteAnimationTargets targets) {
+ mRemoteTargetHandles = createHandles(context, sizingStrategy, targets.apps.length);
+ }
+
+ /**
+ * Use this constructor if you want the number of handles created to match the number of active
+ * running tasks
+ */
+ public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
+ int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds();
+ mRemoteTargetHandles = createHandles(context, sizingStrategy, splitIds.length == 2 ? 2 : 1);
+ }
+
+ private RemoteTargetHandle[] createHandles(Context context,
+ BaseActivityInterface sizingStrategy, int numHandles) {
+ RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles];
+ for (int i = 0; i < numHandles; i++) {
+ TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy);
+ TransformParams transformParams = new TransformParams();
+ handles[i] = new RemoteTargetHandle(tvs, transformParams);
+ }
+ return handles;
+ }
+
+ /**
+ * Pairs together {@link TaskViewSimulator}s and {@link TransformParams} into a
+ * {@link RemoteTargetHandle}
+ * Assigns only the apps associated with {@param targets} into their own TaskViewSimulators.
+ * Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
+ *
+ * If split screen may be active when this is called, you might want to use
+ * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
+ */
+ public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
+ for (int i = 0; i < mRemoteTargetHandles.length; i++) {
+ RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[i];
+ mRemoteTargetHandles[i].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(primaryTaskTarget, targets));
+ mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+ }
+ return mRemoteTargetHandles;
+ }
+
+ /**
+ * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
+ * apps in targets.apps to that of the split screened tasks. If split screen is active, then
+ * {@link #mRemoteTargetHandles} index 0 will be the left/top task, index one right/bottom
+ */
+ public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
+ int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds();
+ RemoteAnimationTargetCompat primaryTaskTarget;
+ RemoteAnimationTargetCompat secondaryTaskTarget;
+ if (mRemoteTargetHandles.length == 1) {
+ // If we're not in split screen, the splitIds count doesn't really matter since we
+ // should always hit this case. Right now there's no use case for multiple app targets
+ // without being in split screen
+ primaryTaskTarget = targets.apps[0];
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+ } else {
+ // split screen
+ primaryTaskTarget = targets.findTask(splitIds[0]);
+ secondaryTaskTarget = targets.findTask(splitIds[1]);
+
+ mStagedSplitBounds = new SplitConfigurationOptions.StagedSplitBounds(
+ primaryTaskTarget.screenSpaceBounds,
+ secondaryTaskTarget.screenSpaceBounds);
+ mRemoteTargetHandles[0].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(primaryTaskTarget, targets));
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
+ mStagedSplitBounds);
+
+ mRemoteTargetHandles[1].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTarget(secondaryTaskTarget, targets));
+ mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget,
+ mStagedSplitBounds);
+ }
+ return mRemoteTargetHandles;
+ }
+
+ private RemoteAnimationTargets createRemoteAnimationTargetsForTarget(
+ RemoteAnimationTargetCompat target,
+ RemoteAnimationTargets targets) {
+ return new RemoteAnimationTargets(new RemoteAnimationTargetCompat[]{target},
+ targets.wallpapers, targets.nonApps, targets.targetMode);
+ }
+
+ public RemoteTargetHandle[] getRemoteTargetHandles() {
+ return mRemoteTargetHandles;
+ }
+
+ public SplitConfigurationOptions.StagedSplitBounds getStagedSplitBounds() {
+ return mStagedSplitBounds;
+ }
+
+ /**
+ * Container to keep together all the associated objects whose properties need to be updated to
+ * animate a single remote app target
+ */
+ public static class RemoteTargetHandle {
+ private final TaskViewSimulator mTaskViewSimulator;
+ private final TransformParams mTransformParams;
+ @Nullable
+ private AnimatorControllerWithResistance mPlaybackController;
+
+ public RemoteTargetHandle(TaskViewSimulator taskViewSimulator,
+ TransformParams transformParams) {
+ mTransformParams = transformParams;
+ mTaskViewSimulator = taskViewSimulator;
+ }
+
+ public TaskViewSimulator getTaskViewSimulator() {
+ return mTaskViewSimulator;
+ }
+
+ public TransformParams getTransformParams() {
+ return mTransformParams;
+ }
+
+ @Nullable
+ public AnimatorControllerWithResistance getPlaybackController() {
+ return mPlaybackController;
+ }
+
+ public void setPlaybackController(
+ @Nullable AnimatorControllerWithResistance playbackController) {
+ mPlaybackController = playbackController;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 678b176..35efddf 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
@@ -25,7 +26,6 @@
import android.content.Context;
import android.content.res.Resources;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
@@ -35,7 +35,6 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.quickstep.util.RecentsOrientedState;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -146,10 +145,10 @@
mDisplayController = DisplayController.INSTANCE.get(mContext);
Resources resources = mContext.getResources();
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(mContext);
- mDisplayId = mDisplayController.getInfo().id;
+ mDisplayId = DEFAULT_DISPLAY;
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
- () -> QuickStepContract.getWindowCornerRadius(resources));
+ () -> QuickStepContract.getWindowCornerRadius(mContext));
// Register for navigation mode changes
SysUINavigationMode.Mode newMode = mSysUiNavMode.addModeChangeListener(this);
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 4495455..ebe46fe 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT;
import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
import android.animation.Animator;
@@ -36,8 +37,10 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.AppCloseConfig;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RectFSpringAnim2;
import com.android.quickstep.util.TaskViewSimulator;
@@ -46,18 +49,22 @@
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
-public abstract class SwipeUpAnimationLogic {
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public abstract class SwipeUpAnimationLogic implements
+ RecentsAnimationCallbacks.RecentsAnimationListener{
protected static final Rect TEMP_RECT = new Rect();
+ protected final RemoteTargetGluer mTargetGluer;
protected DeviceProfile mDp;
protected final Context mContext;
protected final RecentsAnimationDeviceState mDeviceState;
protected final GestureState mGestureState;
- protected final TaskViewSimulator mTaskViewSimulator;
- protected final TransformParams mTransformParams;
+ protected RemoteTargetHandle[] mRemoteTargetHandles;
// Shift in the range of [0, 1].
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
@@ -70,37 +77,48 @@
// How much further we can drag past recents, as a factor of mTransitionDragLength.
protected float mDragLengthFactor = 1;
- protected AnimatorControllerWithResistance mWindowTransitionController;
+ protected boolean mIsSwipeForStagedSplit;
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState, TransformParams transformParams) {
mContext = context;
mDeviceState = deviceState;
mGestureState = gestureState;
- mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
- mTransformParams = transformParams;
- mTaskViewSimulator.getOrientationState().update(
- mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
- mDeviceState.getRotationTouchHelper().getDisplayRotation());
+ mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
+ LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds().length > 1;
+
+ mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
+ mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
+ mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+ mDeviceState.getRotationTouchHelper().getDisplayRotation()
+ ));
}
protected void initTransitionEndpoints(DeviceProfile dp) {
mDp = dp;
-
- mTaskViewSimulator.setDp(dp);
mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
- dp, mContext, TEMP_RECT,
- mTaskViewSimulator.getOrientationState().getOrientationHandler());
+ dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState().getOrientationHandler());
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
- PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
- mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
- AnimatorPlaybackController normalController = pa.createPlaybackController();
- mWindowTransitionController = AnimatorControllerWithResistance.createForRecents(
- normalController, mContext, mTaskViewSimulator.getOrientationState(),
- mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
- mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE);
+ for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
+ PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
+ TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
+ taskViewSimulator.setDp(dp);
+ taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR);
+ AnimatorPlaybackController playbackController =
+ pendingAnimation.createPlaybackController();
+
+ remoteHandle.setPlaybackController(AnimatorControllerWithResistance.createForRecents(
+ playbackController, mContext, taskViewSimulator.getOrientationState(),
+ mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
+ taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE
+ ));
+ }
}
@UiThread
@@ -125,7 +143,9 @@
public abstract void updateFinalShift();
protected PagedOrientationHandler getOrientationHandler() {
- return mTaskViewSimulator.getOrientationState().getOrientationHandler();
+ // OrientationHandler should be independent of remote target, can directly take one
+ return mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState().getOrientationHandler();
}
protected abstract class HomeAnimationFactory {
@@ -207,16 +227,34 @@
* @param startProgress The progress of {@link #mCurrentShift} to start thw window from.
* @return {@link RectF} represents the bounds as starting point in window space.
*/
- protected RectF updateProgressForStartRect(Matrix outMatrix, float startProgress) {
+ protected RectF[] updateProgressForStartRect(Matrix outMatrix, float startProgress) {
mCurrentShift.updateValue(startProgress);
- mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
- RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+ RectF[] startRects = new RectF[mRemoteTargetHandles.length];
+ for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
+ i < mRemoteTargetHandlesLength; i++) {
+ RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
+ TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator();
+ tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress));
- mTaskViewSimulator.applyWindowToHomeRotation(outMatrix);
+ startRects[i] = new RectF(tvs.getCurrentCropRect());
+ tvs.applyWindowToHomeRotation(outMatrix);
+ tvs.getCurrentMatrix().mapRect(startRects[i]);
+ }
+ return startRects;
+ }
- final RectF startRect = new RectF(cropRectF);
- mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
- return startRect;
+ /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
+ protected void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
+ for (RemoteTargetHandle handle : mRemoteTargetHandles) {
+ consumer.accept(handle);
+ }
+ }
+
+ /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */
+ protected TaskViewSimulator[] getRemoteTaskViewSimulators() {
+ return Arrays.stream(mRemoteTargetHandles)
+ .map(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator())
+ .toArray(TaskViewSimulator[]::new);
}
/**
@@ -224,14 +262,28 @@
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
* @param homeAnimationFactory The home animation factory.
*/
- protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
+ // TODO(b/195473584) compute separate end targets for different staged split
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
-
+ RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length];
Matrix homeToWindowPositionMap = new Matrix();
- final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
- RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
+ RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
+ for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
+ i < mRemoteTargetHandlesLength; i++) {
+ RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
+ out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
+ targetRect, remoteHandle.getTransformParams(),
+ remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap);
+ }
+ return out;
+ }
+ private RectFSpringAnim getWindowAnimationToHomeInternal(
+ HomeAnimationFactory homeAnimationFactory, RectF targetRect,
+ TransformParams transformParams, TaskViewSimulator taskViewSimulator,
+ RectF startRect, Matrix homeToWindowPositionMap) {
+ RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
// Move the startRect to Launcher space as floatingIconView runs in Launcher
Matrix windowToHomePositionMap = new Matrix();
homeToWindowPositionMap.invert(windowToHomePositionMap);
@@ -240,7 +292,7 @@
RectFSpringAnim anim;
if (PROTOTYPE_APP_CLOSE.get()) {
anim = new RectFSpringAnim2(startRect, targetRect, mContext,
- mTaskViewSimulator.getCurrentCornerRadius(),
+ taskViewSimulator.getCurrentCornerRadius(),
homeAnimationFactory.getEndRadius(cropRectF));
} else {
anim = new RectFSpringAnim(startRect, targetRect, mContext);
@@ -248,9 +300,10 @@
homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
- homeAnimationFactory, cropRectF, homeToWindowPositionMap);
- anim.addOnUpdateListener(runner);
+ homeAnimationFactory, cropRectF, homeToWindowPositionMap,
+ transformParams, taskViewSimulator);
anim.addAnimatorListener(runner);
+ anim.addOnUpdateListener(runner);
return anim;
}
@@ -262,6 +315,7 @@
final RectF mWindowCurrentRect = new RectF();
final Matrix mHomeToWindowPositionMap;
+ private final TransformParams mLocalTransformParams;
final HomeAnimationFactory mAnimationFactory;
final AnimatorPlaybackController mHomeAnim;
@@ -271,17 +325,19 @@
final float mEndRadius;
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
- Matrix homeToWindowPositionMap) {
+ Matrix homeToWindowPositionMap, TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator) {
mAnimationFactory = factory;
mHomeAnim = factory.createActivityAnimationToHome();
mCropRectF = cropRectF;
mHomeToWindowPositionMap = homeToWindowPositionMap;
+ mLocalTransformParams = transformParams;
cropRectF.roundOut(mCropRect);
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
// rounding at the end of the animation.
- mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
+ mStartRadius = taskViewSimulator.getCurrentCornerRadius();
mEndRadius = factory.getEndRadius(cropRectF);
}
@@ -300,10 +356,11 @@
if (mAnimationFactory.keepWindowOpaque()) {
alpha = 1f;
}
- mTransformParams
+ mLocalTransformParams
.setTargetAlpha(alpha)
.setCornerRadius(cornerRadius);
- mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
+ mLocalTransformParams.applySurfaceParams(mLocalTransformParams
+ .createSurfaceParams(this));
mAnimationFactory.update(config, currentRect, progress,
mMatrix.mapRadius(cornerRadius));
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 11ca4b1..aea2d4c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
@@ -532,10 +533,17 @@
}
}
- public void exitSplitScreen() {
+ /**
+ * To be called whenever the user exits out of split screen apps (either by launching another
+ * app or by swiping home)
+ * @param topTaskId The taskId of the new app that was launched. System will then move this task
+ * to the front of what the user sees while removing all other split stages.
+ * If swiping to home (or there is no task to put at the top), can pass in -1.
+ */
+ public void exitSplitScreen(int topTaskId) {
if (mSplitScreen != null) {
try {
- mSplitScreen.exitSplitScreen();
+ mSplitScreen.exitSplitScreen(topTaskId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call exitSplitScreen");
}
@@ -632,10 +640,11 @@
* @param cancel true if recents starting is being cancelled.
* @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
*/
- public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) {
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
if (mSplitScreen != null) {
try {
- return mSplitScreen.onGoingToRecentsLegacy(cancel);
+ return mSplitScreen.onGoingToRecentsLegacy(cancel, apps);
} catch (RemoteException e) {
Log.w(TAG, "Failed call onGoingToRecentsLegacy");
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index f49c9b0..978fb57 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -107,6 +107,13 @@
public static void addSplitOptions(List<SystemShortcut> outShortcuts,
BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
+ int[] taskViewTaskIds = taskView.getTaskIds();
+ boolean alreadyHasMultipleTasks = taskViewTaskIds[0] != -1 &&
+ taskViewTaskIds[1] != -1;
+ if (alreadyHasMultipleTasks) {
+ return;
+ }
+
PagedOrientationHandler orientationHandler =
taskView.getRecentsView().getPagedOrientationHandler();
List<SplitPositionOption> positions =
@@ -218,6 +225,11 @@
}
}
+ private void enterSplitSelect() {
+ RecentsView overviewPanel = mThumbnailView.getTaskView().getRecentsView();
+ overviewPanel.initiateSplitSelect(mThumbnailView.getTaskView());
+ }
+
/**
* Called when the overlay is no longer used.
*/
@@ -323,6 +335,10 @@
public void onScreenshot() {
endLiveTileMode(() -> saveScreenshot(mTask));
}
+
+ public void onSplit() {
+ endLiveTileMode(TaskOverlay.this::enterSplitSelect);
+ }
}
}
@@ -336,5 +352,8 @@
/** User has indicated they want to screenshot the current task. */
void onScreenshot();
+
+ /** User wants to start split screen with current app. */
+ void onSplit();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index a078bf3..559125e 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -100,7 +100,7 @@
private SplitPositionOption mSplitPositionOption;
public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
SplitPositionOption option) {
- super(option.mIconResId, option.mTextResId, target, taskView.getItemInfo());
+ super(option.iconResId, option.textResId, target, taskView.getItemInfo());
mTaskView = taskView;
mSplitPositionOption = option;
setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index b5da097..23dc913 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -55,7 +55,6 @@
import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
@@ -67,6 +66,7 @@
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
@@ -151,27 +151,7 @@
RemoteAnimationTargetCompat[] wallpaperTargets,
RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
PendingAnimation out) {
- boolean isRunningTask = v.isRunningTask();
- TransformParams params = null;
- TaskViewSimulator tsv = null;
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
- params = v.getRecentsView().getLiveTileParams();
- tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
- }
- createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets,
- depthController, out, params, tsv);
- }
-
- /**
- * Creates an animation that controls the window of the opening targets for the recents launch
- * animation.
- */
- public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
- PendingAnimation out, @Nullable TransformParams params,
- @Nullable TaskViewSimulator tsv) {
+ RecentsView recentsView = v.getRecentsView();
boolean isQuickSwitch = v.isEndQuickswitchCuj();
v.setEndQuickswitchCuj(false);
@@ -182,16 +162,27 @@
inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
final RemoteAnimationTargetCompat navBarTarget = targets.getNavBarRemoteAnimationTarget();
- if (params == null) {
- SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
- targets.addReleaseCheck(applier);
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+ targets.addReleaseCheck(applier);
- params = new TransformParams()
- .setSyncTransactionApplier(applier)
- .setTargetSet(targets);
+ RemoteTargetHandle[] remoteTargetHandles;
+ RemoteTargetHandle[] recentsViewHandles = recentsView.getRemoteTargetHandles();
+ if (v.isRunningTask()) {
+ // Re-use existing handles
+ remoteTargetHandles = recentsViewHandles;
+ } else {
+ RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
+ recentsView.getSizeStrategy(), targets);
+ if (recentsViewHandles != null && recentsViewHandles.length > 1) {
+ remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets);
+ } else {
+ remoteTargetHandles = gluer.assignTargets(targets);
+ }
+ }
+ for (RemoteTargetHandle remoteTargetGluer : remoteTargetHandles) {
+ remoteTargetGluer.getTransformParams().setSyncTransactionApplier(applier);
}
- final RecentsView recentsView = v.getRecentsView();
int taskIndex = recentsView.indexOfChild(v);
Context context = v.getContext();
DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
@@ -201,45 +192,51 @@
float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
int startScroll = recentsView.getScrollOffset(taskIndex);
- TaskViewSimulator topMostSimulator = null;
+ RemoteTargetHandle[] topMostSimulators = null;
- if (tsv == null && targets.apps.length > 0) {
- tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
- tsv.setDp(dp);
+ if (!v.isRunningTask()) {
+ // TVSs already initialized from the running task, no need to re-init
+ for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
+ TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
+ tvsLocal.setDp(dp);
- // RecentsView never updates the display rotation until swipe-up so the value may
- // be stale. Use the display value instead.
- int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
- tsv.getOrientationState().update(displayRotation, displayRotation);
+ // RecentsView never updates the display rotation until swipe-up so the value may
+ // be stale. Use the display value instead.
+ int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
+ tvsLocal.getOrientationState().update(displayRotation, displayRotation);
- tsv.setPreview(targets.apps[targets.apps.length - 1]);
- tsv.fullScreenProgress.value = 0;
- tsv.recentsViewScale.value = 1;
- if (showAsGrid) {
- tsv.taskSecondaryTranslation.value = gridTranslationSecondary;
+ tvsLocal.fullScreenProgress.value = 0;
+ tvsLocal.recentsViewScale.value = 1;
+ if (showAsGrid) {
+ tvsLocal.taskSecondaryTranslation.value = gridTranslationSecondary;
+ }
+ tvsLocal.setScroll(startScroll);
+
+ // Fade in the task during the initial 20% of the animation
+ out.addFloat(targetHandle.getTransformParams(), TransformParams.TARGET_ALPHA, 0, 1,
+ clampToProgress(LINEAR, 0, 0.2f));
}
- tsv.setScroll(startScroll);
-
- // Fade in the task during the initial 20% of the animation
- out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
- clampToProgress(LINEAR, 0, 0.2f));
}
- if (tsv != null) {
- out.setFloat(tsv.fullScreenProgress,
+ for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
+ TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
+ out.setFloat(tvsLocal.fullScreenProgress,
AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
- out.setFloat(tsv.recentsViewScale,
- AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+ out.setFloat(tvsLocal.recentsViewScale,
+ AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
+ TOUCH_RESPONSE_INTERPOLATOR);
if (showAsGrid) {
- out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
+ out.setFloat(tvsLocal.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
}
- out.setFloat(tsv.recentsViewScroll, AnimatedFloat.VALUE, 0,
+ out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
TOUCH_RESPONSE_INTERPOLATOR);
- TaskViewSimulator finalTsv = tsv;
- TransformParams finalParams = params;
- out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+ out.addOnFrameCallback(() -> {
+ for (RemoteTargetHandle handle : remoteTargetHandles) {
+ handle.getTaskViewSimulator().apply(handle.getTransformParams());
+ }
+ });
if (navBarTarget != null) {
final Rect cropRect = new Rect();
out.addOnFrameListener(new MultiValueUpdateListener() {
@@ -252,15 +249,20 @@
public void onUpdate(float percent, boolean initOnly) {
final SurfaceParams.Builder navBuilder =
new SurfaceParams.Builder(navBarTarget.leash);
- if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
- finalTsv.getCurrentCropRect().round(cropRect);
- navBuilder.withMatrix(finalTsv.getCurrentMatrix())
- .withWindowCrop(cropRect)
- .withAlpha(mNavFadeIn.value);
- } else {
- navBuilder.withAlpha(mNavFadeOut.value);
+
+ // TODO Do we need to operate over multiple TVSs for the navbar leash?
+ for (RemoteTargetHandle handle : remoteTargetHandles) {
+ if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
+ TaskViewSimulator taskViewSimulator = handle.getTaskViewSimulator();
+ taskViewSimulator.getCurrentCropRect().round(cropRect);
+ navBuilder.withMatrix(taskViewSimulator.getCurrentMatrix())
+ .withWindowCrop(cropRect)
+ .withAlpha(mNavFadeIn.value);
+ } else {
+ navBuilder.withAlpha(mNavFadeOut.value);
+ }
+ handle.getTransformParams().applySurfaceParams(navBuilder.build());
}
- finalParams.applySurfaceParams(navBuilder.build());
}
});
} else if (inLiveTileMode) {
@@ -272,14 +274,16 @@
controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION);
}
}
- topMostSimulator = tsv;
+ topMostSimulators = remoteTargetHandles;
}
- if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
+ if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulators.length > 0) {
out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
- TaskViewSimulator simulatorToCopy = topMostSimulator;
- simulatorToCopy.apply(params);
+ RemoteTargetHandle[] simulatorCopies = topMostSimulators;
+ for (RemoteTargetHandle handle : simulatorCopies) {
+ handle.getTaskViewSimulator().apply(handle.getTransformParams());
+ }
// Mt represents the overall transformation on the thumbnailView relative to the
// Launcher's rootView
@@ -293,36 +297,49 @@
// During animation we apply transformation on the thumbnailView (and not the rootView)
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
// Mt K(0)` K(t) Mt`
- TaskThumbnailView ttv = v.getThumbnail();
- RectF tvBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
- float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
- getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
- RectF tvBoundsInRoot = new RectF(
- tvBoundsMapped[0], tvBoundsMapped[1],
- tvBoundsMapped[2], tvBoundsMapped[3]);
+ TaskThumbnailView[] thumbnails = v.getThumbnails();
+ Matrix[] mt = new Matrix[simulatorCopies.length];
+ Matrix[] mti = new Matrix[simulatorCopies.length];
+ for (int i = 0; i < thumbnails.length; i++) {
+ TaskThumbnailView ttv = thumbnails[i];
+ RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
+ float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
+ getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
+ RectF localBoundsInRoot = new RectF(
+ tvBoundsMapped[0], tvBoundsMapped[1],
+ tvBoundsMapped[2], tvBoundsMapped[3]);
+ Matrix localMt = new Matrix();
+ localMt.setRectToRect(localBounds, localBoundsInRoot, ScaleToFit.FILL);
+ mt[i] = localMt;
- Matrix mt = new Matrix();
- mt.setRectToRect(tvBounds, tvBoundsInRoot, ScaleToFit.FILL);
+ Matrix localMti = new Matrix();
+ localMti.invert(localMt);
+ mti[i] = localMti;
+ }
- Matrix mti = new Matrix();
- mt.invert(mti);
-
- Matrix k0i = new Matrix();
- simulatorToCopy.getCurrentMatrix().invert(k0i);
-
+ Matrix[] k0i = new Matrix[simulatorCopies.length];
+ for (int i = 0; i < simulatorCopies.length; i++) {
+ k0i[i] = new Matrix();
+ simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]);
+ }
Matrix animationMatrix = new Matrix();
out.addOnFrameCallback(() -> {
- animationMatrix.set(mt);
- animationMatrix.postConcat(k0i);
- animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix());
- animationMatrix.postConcat(mti);
- ttv.setAnimationMatrix(animationMatrix);
+ for (int i = 0; i < simulatorCopies.length; i++) {
+ animationMatrix.set(mt[i]);
+ animationMatrix.postConcat(k0i[i]);
+ animationMatrix.postConcat(simulatorCopies[i]
+ .getTaskViewSimulator().getCurrentMatrix());
+ animationMatrix.postConcat(mti[i]);
+ thumbnails[i].setAnimationMatrix(animationMatrix);
+ }
});
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- ttv.setAnimationMatrix(null);
+ for (TaskThumbnailView ttv : thumbnails) {
+ ttv.setAnimationMatrix(null);
+ }
}
});
}
@@ -365,8 +382,8 @@
* device is considered in multiWindowMode and things like insets and stuff change
* and calculations have to be adjusted in the animations for that
*/
- public static void composeRecentsSplitLaunchAnimator(@NonNull TaskView initialView,
- @NonNull TaskView v, @NonNull TransitionInfo transitionInfo,
+ public static void composeRecentsSplitLaunchAnimator(@NonNull Task initalTask,
+ @NonNull Task secondTask, @NonNull TransitionInfo transitionInfo,
SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
final TransitionInfo.Change[] splitRoots = new TransitionInfo.Change[2];
@@ -376,7 +393,7 @@
final int mode = change.getMode();
// Find the target tasks' root tasks since those are the split stages that need to
// be animated (the tasks themselves are children and thus inherit animation).
- if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) {
+ if (taskId == initalTask.key.id || taskId == secondTask.key.id) {
if (!(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
throw new IllegalStateException(
"Expected task to be showing, but it is " + mode);
@@ -385,7 +402,7 @@
throw new IllegalStateException("Initiating multi-split launch but the split"
+ "root of " + taskId + " is already visible or has broken hierarchy.");
}
- splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] =
+ splitRoots[taskId == initalTask.key.id ? 0 : 1] =
transitionInfo.getChange(change.getParent());
}
}
@@ -405,8 +422,8 @@
}
/** Legacy version (until shell transitions are enabled) */
- public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull TaskView initialView,
- @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
+ public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull Task initialTask,
+ @NonNull Task secondTask, @NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
@NonNull RemoteAnimationTargetCompat[] nonAppTargets,
@NonNull Runnable finishCallback) {
@@ -415,12 +432,12 @@
for (int i = 0; i < appTargets.length; ++i) {
final int taskId = appTargets[i].taskInfo != null ? appTargets[i].taskInfo.taskId : -1;
final int mode = appTargets[i].mode;
- if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) {
+ if (taskId == initialTask.key.id || taskId == secondTask.key.id) {
if (mode != MODE_OPENING) {
throw new IllegalStateException(
"Expected task to be opening, but it is " + mode);
}
- splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] = i;
+ splitRoots[taskId == initialTask.key.id ? 0 : 1] = i;
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 61622ee..3ce12c5 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -99,7 +99,9 @@
import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.SplitScreenBounds;
import com.android.systemui.plugins.OverscrollPlugin;
import com.android.systemui.plugins.PluginListener;
@@ -268,6 +270,12 @@
MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
}
+ @BinderThread
+ @Override
+ public void onScreenTurnedOn() {
+ MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
+ }
+
@Override
public void onRotationProposal(int rotation, boolean isValid) {
executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
@@ -350,13 +358,16 @@
mDeviceState = new RecentsAnimationDeviceState(this, true);
mDisplayManager = getSystemService(DisplayManager.class);
mTaskbarManager = new TaskbarManager(this);
-
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
- mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
- mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
+
+ // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
+ mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+ mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
+
ProtoTracer.INSTANCE.get(this).add(this);
+ LauncherSplitScreenListener.INSTANCE.get(this).init();
sConnected = true;
}
@@ -513,6 +524,7 @@
getSystemService(AccessibilityManager.class)
.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
+ LauncherSplitScreenListener.INSTANCE.get(this).destroy();
mTaskbarManager.destroy();
sConnected = false;
super.onDestroy();
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 64a428f..765480c 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -79,8 +79,10 @@
* to the home task. This allows us to handle quick-switch similarly to a quick-switching
* from a foreground task.
*/
- public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
- mHomeTaskInfo = homeTaskInfo;
+ public void onGestureAnimationStartOnHome(RunningTaskInfo[] homeTaskInfo) {
+ // TODO(b/195607777) General fallback love, but this might be correct
+ // Home task should be defined as the front-most task info I think?
+ mHomeTaskInfo = homeTaskInfo[0];
onGestureAnimationStart(homeTaskInfo);
}
@@ -92,8 +94,8 @@
@Override
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
- TaskViewSimulator taskViewSimulator) {
- super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulator);
+ TaskViewSimulator[] taskViewSimulators) {
+ super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulators);
if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
TaskView tv = getTaskViewByTaskId(mHomeTaskInfo.taskId);
if (tv != null) {
@@ -133,7 +135,13 @@
}
@Override
- protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
+ protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
+ if (runningTaskInfos.length > 1) {
+ // can't be in split screen w/ home task
+ return super.shouldAddStubTaskView(runningTaskInfos);
+ }
+
+ RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
if (mHomeTaskInfo != null && runningTaskInfo != null &&
mHomeTaskInfo.taskId == runningTaskInfo.taskId
&& getTaskViewCount() == 0) {
@@ -141,7 +149,7 @@
// show the empty recents message instead of showing a stub task and later removing it.
return false;
}
- return super.shouldAddStubTaskView(runningTaskInfo);
+ return super.shouldAddStubTaskView(runningTaskInfos);
}
@Override
@@ -149,6 +157,7 @@
// When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
+ // TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
int runningTaskId = getTaskIdsForRunningTaskView()[0];
if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId && !tasks.isEmpty()) {
// Check if the task list has running task
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 725c7c4..e6285f2 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -50,6 +50,7 @@
import androidx.annotation.UiThread;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.tracing.InputConsumerProto;
@@ -356,6 +357,9 @@
}
case ACTION_CANCEL:
case ACTION_UP: {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "1");
+ }
if (DEBUG_FAILED_QUICKSWITCH && !mPassedWindowMoveSlop) {
float displacementX = mLastPos.x - mDownPos.x;
float displacementY = mLastPos.y - mDownPos.y;
@@ -409,6 +413,9 @@
* the animation can still be running.
*/
private void finishTouchTracking(MotionEvent ev) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "2");
+ }
Object traceToken = TraceHelper.INSTANCE.beginSection(UP_EVT,
FLAG_CHECK_FOR_RACE_CONDITIONS);
diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
index 957f776..2f3a912 100644
--- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
@@ -68,7 +68,6 @@
showFeedback(R.string.assistant_gesture_feedback_swipe_too_far_from_corner);
break;
case ASSISTANT_COMPLETED:
- hideFeedback(true);
showRippleEffect(null);
showFeedback(R.string.assistant_gesture_tutorial_playground_subtitle);
break;
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 3cb22f4..f2402e3 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -18,10 +18,9 @@
import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE;
+import android.annotation.LayoutRes;
import android.graphics.PointF;
-import androidx.appcompat.content.res.AppCompatResources;
-
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
@@ -44,8 +43,18 @@
}
@Override
- protected int getMockAppTaskThumbnailResId(boolean forDarkMode) {
- return R.drawable.mock_conversation;
+ protected int getMockAppTaskLayoutResId() {
+ return getMockAppTaskCurrentPageLayoutResId();
+ }
+
+ @LayoutRes
+ int getMockAppTaskCurrentPageLayoutResId() {
+ return R.layout.gesture_tutorial_mock_conversation;
+ }
+
+ @LayoutRes
+ int getMockAppTaskPreviousPageLayoutResId() {
+ return R.layout.gesture_tutorial_mock_conversation_list;
}
@Override
@@ -70,10 +79,8 @@
switch (result) {
case BACK_COMPLETED_FROM_LEFT:
case BACK_COMPLETED_FROM_RIGHT:
- mTutorialFragment.releaseGestureVideoView();
- hideFeedback(true);
- mFakeTaskView.setBackground(AppCompatResources.getDrawable(mContext,
- R.drawable.mock_conversations_list));
+ mTutorialFragment.releaseFeedbackAnimation();
+ updateFakeAppTaskViewLayout(getMockAppTaskPreviousPageLayoutResId());
int subtitleResId = mTutorialFragment.isAtFinalStep()
? R.string.back_gesture_feedback_complete_without_follow_up
: R.string.back_gesture_feedback_complete_with_overview_follow_up;
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index 1740f68..f54734d 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -15,6 +15,10 @@
*/
package com.android.quickstep.interaction;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.view.MotionEvent;
import android.view.View;
@@ -23,18 +27,76 @@
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
+import java.util.ArrayList;
+
/** Shows the Back gesture interactive tutorial. */
public class BackGestureTutorialFragment extends TutorialFragment {
+
@Nullable
@Override
- Integer getFeedbackVideoResId(boolean forDarkMode) {
- return R.drawable.gesture_tutorial_motion_back;
+ Integer getEdgeAnimationResId() {
+ return R.drawable.gesture_tutorial_loop_back;
}
@Nullable
@Override
- Integer getGestureVideoResId() {
- return R.drawable.gesture_tutorial_loop_back;
+ protected Animator createGestureAnimation() {
+ if (!(mTutorialController instanceof BackGestureTutorialController)) {
+ return null;
+ }
+ BackGestureTutorialController controller =
+ (BackGestureTutorialController) mTutorialController;
+ float fingerDotStartTranslationX = (float) -(mRootView.getWidth() / 2);
+
+ AnimatorSet fingerDotAppearanceAnimator = controller.createFingerDotAppearanceAnimatorSet();
+ fingerDotAppearanceAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mFingerDotView.setTranslationX(fingerDotStartTranslationX);
+ }
+ });
+
+ ObjectAnimator translationAnimator = ObjectAnimator.ofFloat(
+ mFingerDotView, View.TRANSLATION_X, fingerDotStartTranslationX, 0);
+ translationAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ controller.updateFakeAppTaskViewLayout(
+ controller.getMockAppTaskPreviousPageLayoutResId());
+ }
+ });
+ translationAnimator.setDuration(1000);
+
+ Animator animationPause = controller.createAnimationPause();
+ animationPause.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ controller.updateFakeAppTaskViewLayout(
+ controller.getMockAppTaskCurrentPageLayoutResId());
+ }
+ });
+ ArrayList<Animator> animators = new ArrayList<>();
+
+ animators.add(fingerDotAppearanceAnimator);
+ animators.add(translationAnimator);
+ animators.add(controller.createFingerDotDisappearanceAnimatorSet());
+ animators.add(animationPause);
+
+ AnimatorSet finalAnimation = new AnimatorSet();
+ finalAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ controller.updateFakeAppTaskViewLayout(
+ controller.getMockAppTaskCurrentPageLayoutResId());
+ }
+ });
+ finalAnimation.playSequentially(animators);
+
+ return finalAnimation;
}
@Override
@@ -49,6 +111,7 @@
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
+ releaseFeedbackAnimation();
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && mTutorialController != null) {
mTutorialController.setRippleHotspot(motionEvent.getX(), motionEvent.getY());
}
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 0521db4..b2b2f59 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -43,7 +43,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.VibratorWrapper;
/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
public class EdgeBackGesturePanel extends View {
@@ -282,9 +282,7 @@
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
int currentNightMode =
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- mPaint.setColor(context.getColor(currentNightMode == Configuration.UI_MODE_NIGHT_YES
- ? R.color.back_arrow_color_light
- : R.color.back_arrow_color_dark));
+ mPaint.setColor(context.getColor(R.color.gesture_tutorial_back_arrow_color));
loadDimens();
updateArrowDirection();
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 819c91c..307a8fd 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -42,8 +42,8 @@
}
@Override
- protected int getMockAppTaskThumbnailResId(boolean forDarkMode) {
- return forDarkMode ? R.drawable.mock_webpage_dark_mode : R.drawable.mock_webpage_light_mode;
+ protected int getMockAppTaskLayoutResId() {
+ return R.layout.gesture_tutorial_mock_webpage;
}
@Override
@@ -80,7 +80,7 @@
case HOME_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED: {
- mTutorialFragment.releaseGestureVideoView();
+ mTutorialFragment.releaseFeedbackAnimation();
animateFakeTaskViewHome(finalVelocity, null);
int subtitleResId = mTutorialFragment.isAtFinalStep()
? R.string.home_gesture_feedback_complete_without_follow_up
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
index 9572637..dcae07d 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -15,25 +15,73 @@
*/
package com.android.quickstep.interaction;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.view.MotionEvent;
+import android.view.View;
+
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
+import java.util.ArrayList;
+
/** Shows the Home gesture interactive tutorial. */
public class HomeGestureTutorialFragment extends TutorialFragment {
+
@Nullable
@Override
- Integer getFeedbackVideoResId(boolean forDarkMode) {
- return forDarkMode
- ? R.drawable.gesture_tutorial_motion_home_dark_mode
- : R.drawable.gesture_tutorial_motion_home_light_mode;
+ Integer getEdgeAnimationResId() {
+ return R.drawable.gesture_tutorial_loop_home;
}
@Nullable
@Override
- Integer getGestureVideoResId() {
- return R.drawable.gesture_tutorial_loop_home;
+ protected Animator createGestureAnimation() {
+ if (!(mTutorialController instanceof HomeGestureTutorialController)) {
+ return null;
+ }
+ float fingerDotStartTranslationY = (float) mRootView.getFullscreenHeight() / 2;
+ HomeGestureTutorialController controller =
+ (HomeGestureTutorialController) mTutorialController;
+
+ AnimatorSet fingerDotAppearanceAnimator = controller.createFingerDotAppearanceAnimatorSet();
+ fingerDotAppearanceAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mFingerDotView.setTranslationY(fingerDotStartTranslationY);
+ }
+ });
+
+ Animator animationPause = controller.createAnimationPause();
+ animationPause.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ controller.resetFakeTaskView();
+ }
+ });
+ ArrayList<Animator> animators = new ArrayList<>();
+
+ animators.add(fingerDotAppearanceAnimator);
+ animators.add(controller.createFingerDotHomeSwipeAnimator(fingerDotStartTranslationY));
+ animators.add(controller.createFingerDotDisappearanceAnimatorSet());
+ animators.add(animationPause);
+
+ AnimatorSet finalAnimation = new AnimatorSet();
+ finalAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ controller.resetFakeTaskView();
+ }
+ });
+ finalAnimation.playSequentially(animators);
+
+ return finalAnimation;
}
@Override
@@ -45,4 +93,10 @@
Class<? extends TutorialController> getControllerClass() {
return HomeGestureTutorialController.class;
}
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ releaseFeedbackAnimation();
+ return super.onTouch(view, motionEvent);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index a9a9e2a..851cccf 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,7 +16,6 @@
package com.android.quickstep.interaction;
import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -26,6 +25,7 @@
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -47,11 +47,11 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
/** Utility class to handle Home and Assistant gestures. */
@@ -120,7 +120,7 @@
mAssistantGestureDetector = new GestureDetector(context, new AssistantGestureListener());
int assistantWidth = resources.getDimensionPixelSize(R.dimen.gestures_assistant_width);
final float assistantHeight = Math.max(mBottomGestureHeight,
- QuickStepContract.getWindowCornerRadius(resources));
+ QuickStepContract.getWindowCornerRadius(context));
mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mDisplaySize.y;
mAssistantLeftRegion.top = mAssistantRightRegion.top = mDisplaySize.y - assistantHeight;
mAssistantLeftRegion.left = 0;
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 77ddb2b..b38a641 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -49,8 +49,8 @@
}
@Override
- protected int getMockAppTaskThumbnailResId(boolean forDarkMode) {
- return R.drawable.mock_conversations_list;
+ protected int getMockAppTaskLayoutResId() {
+ return R.layout.gesture_tutorial_mock_conversation_list;
}
@Override
@@ -98,13 +98,8 @@
showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
break;
case OVERVIEW_GESTURE_COMPLETED:
- mTutorialFragment.releaseGestureVideoView();
- PendingAnimation anim = new PendingAnimation(300);
- anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
- AnimatorSet animset = anim.buildAnim();
- animset.start();
- mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset);
+ mTutorialFragment.releaseFeedbackAnimation();
+ animateTaskViewToOverview();
onMotionPaused(true /*arbitrary value*/);
int subtitleResId = mTutorialFragment.getNumSteps() > 1
&& mTutorialFragment.isAtFinalStep()
@@ -126,4 +121,13 @@
break;
}
}
+
+ public void animateTaskViewToOverview() {
+ PendingAnimation anim = new PendingAnimation(300);
+ anim.setFloat(mTaskViewSwipeUpAnimation
+ .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ AnimatorSet animset = anim.buildAnim();
+ animset.start();
+ mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
index d2ec327..968412b 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -15,25 +15,96 @@
*/
package com.android.quickstep.interaction;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.view.MotionEvent;
+import android.view.View;
+
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
+import java.util.ArrayList;
+
/** Shows the Overview gesture interactive tutorial. */
public class OverviewGestureTutorialFragment extends TutorialFragment {
+
@Nullable
@Override
- Integer getFeedbackVideoResId(boolean forDarkMode) {
- return forDarkMode
- ? R.drawable.gesture_tutorial_motion_overview_dark_mode
- : R.drawable.gesture_tutorial_motion_overview_light_mode;
+ Integer getEdgeAnimationResId() {
+ return R.drawable.gesture_tutorial_loop_overview;
}
@Nullable
@Override
- Integer getGestureVideoResId() {
- return R.drawable.gesture_tutorial_loop_overview;
+ protected Animator createGestureAnimation() {
+ if (!(mTutorialController instanceof OverviewGestureTutorialController)) {
+ return null;
+ }
+ float fingerDotStartTranslationY = (float) mRootView.getFullscreenHeight() / 2;
+ OverviewGestureTutorialController controller =
+ (OverviewGestureTutorialController) mTutorialController;
+
+ AnimatorSet fingerDotAppearanceAnimator = controller.createFingerDotAppearanceAnimatorSet();
+ fingerDotAppearanceAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+
+ mFingerDotView.setTranslationY(fingerDotStartTranslationY);
+ }
+ });
+
+ Animator swipeAnimator =
+ controller.createFingerDotOverviewSwipeAnimator(fingerDotStartTranslationY);
+ swipeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mFakePreviousTaskView.setVisibility(View.VISIBLE);
+ controller.onMotionPaused(true /*arbitrary value*/);
+ }
+ });
+
+ AnimatorSet fingerDotDisappearanceAnimator =
+ controller.createFingerDotDisappearanceAnimatorSet();
+ fingerDotDisappearanceAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ controller.animateTaskViewToOverview();
+ }
+ });
+
+ Animator animationPause = controller.createAnimationPause();
+ animationPause.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ controller.resetFakeTaskView();
+ }
+ });
+ ArrayList<Animator> animators = new ArrayList<>();
+
+ animators.add(fingerDotAppearanceAnimator);
+ animators.add(swipeAnimator);
+ animators.add(controller.createAnimationPause());
+ animators.add(fingerDotDisappearanceAnimator);
+ animators.add(animationPause);
+
+ AnimatorSet finalAnimation = new AnimatorSet();
+ finalAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ controller.resetFakeTaskView();
+ }
+ });
+ finalAnimation.playSequentially(animators);
+
+ return finalAnimation;
}
@Override
@@ -45,4 +116,10 @@
Class<? extends TutorialController> getControllerClass() {
return OverviewGestureTutorialController.class;
}
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ releaseFeedbackAnimation();
+ return super.onTouch(view, motionEvent);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
index db1afc2..ac0c17d 100644
--- a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
+++ b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
@@ -16,8 +16,10 @@
package com.android.quickstep.interaction;
import android.content.Context;
+import android.graphics.Insets;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import android.widget.RelativeLayout;
import androidx.fragment.app.FragmentManager;
@@ -41,4 +43,13 @@
return ((TutorialFragment) FragmentManager.findFragment(this))
.onInterceptTouch(motionEvent);
}
+
+ /**
+ * Returns this view's fullscreen height. This method is agnostic of this view's actual height.
+ */
+ public int getFullscreenHeight() {
+ Insets insets = getRootWindowInsets().getInsets(WindowInsets.Type.systemBars());
+
+ return getHeight() + insets.top + insets.bottom;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index b2183d6..1f75936 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -25,6 +25,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Outline;
@@ -32,7 +33,6 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
-import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -62,23 +62,24 @@
private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12);
+ private static final long HOME_SWIPE_ANIMATION_DURATION_MILLIS = 625;
+ private static final long OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS = 1000;
+
final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation;
private float mFakeTaskViewRadius;
- private Rect mFakeTaskViewRect = new Rect();
+ private final Rect mFakeTaskViewRect = new Rect();
RunningWindowAnim mRunningWindowAnim;
private boolean mShowTasks = false;
private boolean mShowPreviousTasks = false;
- private AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() {
+ private final AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mFakeHotseatView.setVisibility(View.INVISIBLE);
mFakeIconView.setVisibility(View.INVISIBLE);
if (mTutorialFragment.getActivity() != null) {
- DisplayMetrics displayMetrics =
- mTutorialFragment.getResources().getDisplayMetrics();
- int height = displayMetrics.heightPixels;
- int width = displayMetrics.widthPixels;
+ int height = mTutorialFragment.getRootView().getFullscreenHeight();
+ int width = mTutorialFragment.getRootView().getWidth();
mFakeTaskViewRect.set(0, 0, width, height);
}
mFakeTaskViewRadius = 0;
@@ -107,9 +108,8 @@
.copy(mContext);
mTaskViewSwipeUpAnimation.initDp(dp);
- DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
- int height = displayMetrics.heightPixels;
- int width = displayMetrics.widthPixels;
+ int height = mTutorialFragment.getRootView().getFullscreenHeight();
+ int width = mTutorialFragment.getRootView().getWidth();
mFakeTaskViewRect.set(0, 0, width, height);
mFakeTaskViewRadius = 0;
@@ -137,7 +137,6 @@
/** Fades the task view, optionally after animating to a fake Overview. */
void fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset,
@Nullable Runnable onEndRunnable) {
- hideFeedback(true);
cancelRunningAnimation();
PendingAnimation anim = new PendingAnimation(300);
if (toOverviewFirst) {
@@ -183,6 +182,7 @@
}
void resetFakeTaskView() {
+ mFakeTaskView.setVisibility(View.VISIBLE);
PendingAnimation anim = new PendingAnimation(300);
anim.setFloat(mTaskViewSwipeUpAnimation
.getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
@@ -194,7 +194,6 @@
}
void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) {
- hideFeedback(true);
cancelRunningAnimation();
mFakePreviousTaskView.setVisibility(View.INVISIBLE);
mFakeHotseatView.setVisibility(View.VISIBLE);
@@ -217,9 +216,6 @@
if (mGestureCompleted) {
return;
}
- if (displacement != null) {
- hideFeedback(true);
- }
if (mTutorialType == HOME_NAVIGATION_COMPLETE
|| mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
mFakeTaskView.setVisibility(View.INVISIBLE);
@@ -263,14 +259,16 @@
void initDp(DeviceProfile dp) {
initTransitionEndpoints(dp);
- mTaskViewSimulator.setPreviewBounds(
+ mRemoteTargetHandles[0].getTaskViewSimulator().setPreviewBounds(
new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
}
@Override
public void updateFinalShift() {
- mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
- mTaskViewSimulator.apply(mTransformParams);
+ mRemoteTargetHandles[0].getPlaybackController()
+ .setProgress(mCurrentShift.value, mDragLengthFactor);
+ mRemoteTargetHandles[0].getTaskViewSimulator().apply(
+ mRemoteTargetHandles[0].getTransformParams());
}
AnimatedFloat getCurrentShift() {
@@ -326,12 +324,51 @@
mFakeIconView.setVisibility(View.INVISIBLE);
}
};
- RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
+ RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift,
+ homeAnimFactory)[0];
windowAnim.start(mContext, velocityPxPerMs);
return windowAnim;
}
}
+ protected Animator createFingerDotHomeSwipeAnimator(float fingerDotStartTranslationY) {
+ Animator homeSwipeAnimator = createFingerDotSwipeUpAnimator(fingerDotStartTranslationY)
+ .setDuration(HOME_SWIPE_ANIMATION_DURATION_MILLIS);
+
+ homeSwipeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ animateFakeTaskViewHome(
+ new PointF(
+ 0f,
+ fingerDotStartTranslationY / HOME_SWIPE_ANIMATION_DURATION_MILLIS),
+ null);
+ }
+ });
+
+ return homeSwipeAnimator;
+ }
+
+ protected Animator createFingerDotOverviewSwipeAnimator(float fingerDotStartTranslationY) {
+ return createFingerDotSwipeUpAnimator(fingerDotStartTranslationY)
+ .setDuration(OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS);
+ }
+
+
+ private Animator createFingerDotSwipeUpAnimator(float fingerDotStartTranslationY) {
+ ValueAnimator swipeAnimator = ValueAnimator.ofFloat(0f, 1f);
+
+ swipeAnimator.addUpdateListener(valueAnimator -> {
+ float gestureProgress =
+ -fingerDotStartTranslationY * valueAnimator.getAnimatedFraction();
+ setNavBarGestureProgress(gestureProgress);
+ mFingerDotView.setTranslationY(fingerDotStartTranslationY + gestureProgress);
+ });
+
+ return swipeAnimator;
+ }
+
private class FakeTransformParams extends TransformParams {
@Override
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 4b4e7e6..77bfc31 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -16,25 +16,32 @@
package com.android.quickstep.interaction;
import static android.view.View.GONE;
+import static android.view.View.NO_ID;
+import static android.view.View.inflate;
+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.content.pm.PackageManager;
-import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.CallSuper;
import androidx.annotation.DrawableRes;
+import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -48,18 +55,25 @@
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
+import java.util.ArrayList;
+
abstract class TutorialController implements BackGestureAttemptCallback,
NavBarGestureAttemptCallback {
private static final String TAG = "TutorialController";
+ private static final float FINGER_DOT_VISIBLE_ALPHA = 0.6f;
+ private static final float FINGER_DOT_SMALL_SCALE = 0.7f;
+ private static final int FINGER_DOT_ANIMATION_DURATION_MILLIS = 500;
+
private static final String PIXEL_TIPS_APP_PACKAGE_NAME = "com.google.android.apps.tips";
private static final CharSequence DEFAULT_PIXEL_TIPS_APP_NAME = "Pixel Tips";
- private static final int FEEDBACK_ANIMATION_MS = 250;
+ private static final int FEEDBACK_ANIMATION_MS = 133;
private static final int RIPPLE_VISIBLE_MS = 300;
private static final int GESTURE_ANIMATION_DELAY_MS = 1500;
private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 4000;
+ private static final long GESTURE_ANIMATION_PAUSE_DURATION_MILLIS = 1000;
final TutorialFragment mTutorialFragment;
TutorialType mTutorialType;
@@ -68,17 +82,17 @@
final TextView mCloseButton;
final ViewGroup mFeedbackView;
final TextView mFeedbackTitleView;
- final ImageView mFeedbackVideoView;
- final ImageView mGestureVideoView;
+ final ImageView mEdgeGestureVideoView;
final RelativeLayout mFakeLauncherView;
final ImageView mFakeHotseatView;
final ClipIconView mFakeIconView;
- final View mFakeTaskView;
+ final FrameLayout mFakeTaskView;
final View mFakePreviousTaskView;
final View mRippleView;
final RippleDrawable mRippleDrawable;
final Button mActionButton;
final TutorialStepIndicator mTutorialStepView;
+ final ImageView mFingerDotView;
private final AlertDialog mSkipTutorialDialog;
protected boolean mGestureCompleted = false;
@@ -87,7 +101,8 @@
// views before posting new callbacks.
private final Runnable mTitleViewCallback;
@Nullable private Runnable mFeedbackViewCallback;
- @Nullable private Runnable mFeedbackVideoViewCallback;
+ @Nullable private Runnable mFakeTaskViewCallback;
+ private final Runnable mShowFeedbackRunnable;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
@@ -100,8 +115,7 @@
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
mFeedbackTitleView = mFeedbackView.findViewById(
R.id.gesture_tutorial_fragment_feedback_title);
- mFeedbackVideoView = rootView.findViewById(R.id.gesture_tutorial_feedback_video);
- mGestureVideoView = rootView.findViewById(R.id.gesture_tutorial_gesture_video);
+ mEdgeGestureVideoView = rootView.findViewById(R.id.gesture_tutorial_edge_gesture_video);
mFakeLauncherView = rootView.findViewById(R.id.gesture_tutorial_fake_launcher_view);
mFakeHotseatView = rootView.findViewById(R.id.gesture_tutorial_fake_hotseat_view);
mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
@@ -113,10 +127,34 @@
mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button);
mTutorialStepView =
rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step);
+ mFingerDotView = rootView.findViewById(R.id.gesture_tutorial_finger_dot);
mSkipTutorialDialog = createSkipTutorialDialog();
mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ mShowFeedbackRunnable = () -> {
+ mFeedbackView.setAlpha(0f);
+ mFeedbackView.setScaleX(0.95f);
+ mFeedbackView.setScaleY(0.95f);
+ mFeedbackView.setVisibility(View.VISIBLE);
+ mFeedbackView.animate()
+ .setDuration(FEEDBACK_ANIMATION_MS)
+ .alpha(1f)
+ .scaleX(1f)
+ .scaleY(1f)
+ .withEndAction(() -> {
+ if (mGestureCompleted && !mTutorialFragment.isAtFinalStep()) {
+ if (mFeedbackViewCallback != null) {
+ mFeedbackView.removeCallbacks(mFeedbackViewCallback);
+ }
+ mFeedbackViewCallback = mTutorialFragment::continueTutorial;
+ mFeedbackView.postDelayed(mFeedbackViewCallback,
+ ADVANCE_TUTORIAL_TIMEOUT_MS);
+ }
+ })
+ .start();
+ mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
+ };
}
private void showSkipTutorialDialog() {
@@ -134,9 +172,9 @@
return R.drawable.default_sandbox_mock_launcher;
}
- @DrawableRes
- protected int getMockAppTaskThumbnailResId(boolean forDarkMode) {
- return R.drawable.default_sandbox_app_task_thumbnail;
+ @LayoutRes
+ protected int getMockAppTaskLayoutResId() {
+ return View.NO_ID;
}
@DrawableRes
@@ -173,17 +211,10 @@
mFeedbackView.setTranslationY(0);
return;
}
- AnimatedVectorDrawable tutorialAnimation = mTutorialFragment.getTutorialAnimation();
- AnimatedVectorDrawable gestureAnimation = mTutorialFragment.getGestureAnimation();
-
- if (tutorialAnimation != null && gestureAnimation != null) {
- TextView title = mFeedbackView.findViewById(
- R.id.gesture_tutorial_fragment_feedback_title);
-
- playFeedbackVideo(tutorialAnimation, gestureAnimation, () -> {
- mFeedbackView.setTranslationY(0);
- title.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }, true);
+ Animator gestureAnimation = mTutorialFragment.getGestureAnimation();
+ AnimatedVectorDrawable edgeAnimation = mTutorialFragment.getEdgeAnimation();
+ if (gestureAnimation != null && edgeAnimation != null) {
+ playFeedbackAnimation(gestureAnimation, edgeAnimation, mShowFeedbackRunnable, true);
}
}
@@ -215,8 +246,13 @@
int subtitleResId,
boolean isGestureSuccessful,
boolean useGestureAnimationDelay) {
- mFeedbackTitleView.setText(titleResId);
mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
+ if (mFeedbackViewCallback != null) {
+ mFeedbackView.removeCallbacks(mFeedbackViewCallback);
+ mFeedbackViewCallback = null;
+ }
+
+ mFeedbackTitleView.setText(titleResId);
TextView subtitle =
mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_subtitle);
subtitle.setText(subtitleResId);
@@ -226,77 +262,68 @@
showActionButton();
}
- if (mFeedbackVideoViewCallback != null) {
- mFeedbackVideoView.removeCallbacks(mFeedbackVideoViewCallback);
- mFeedbackVideoViewCallback = null;
+ if (mFakeTaskViewCallback != null) {
+ mFakeTaskView.removeCallbacks(mFakeTaskViewCallback);
+ mFakeTaskViewCallback = null;
}
}
mGestureCompleted = isGestureSuccessful;
- AnimatedVectorDrawable tutorialAnimation = mTutorialFragment.getTutorialAnimation();
- AnimatedVectorDrawable gestureAnimation = mTutorialFragment.getGestureAnimation();
- if (tutorialAnimation != null && gestureAnimation != null) {
- if (!isGestureSuccessful) {
- playFeedbackVideo(tutorialAnimation, gestureAnimation, () -> {
- mFeedbackView.setTranslationY(
- -mFeedbackView.getHeight() - mFeedbackView.getTop());
- mFeedbackView.setVisibility(View.VISIBLE);
- mFeedbackView.animate()
- .setDuration(FEEDBACK_ANIMATION_MS)
- .translationY(0)
- .start();
- mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
- }, useGestureAnimationDelay);
- return;
- } else {
- mTutorialFragment.releaseFeedbackVideoView();
- }
+ Animator gestureAnimation = mTutorialFragment.getGestureAnimation();
+ AnimatedVectorDrawable edgeAnimation = mTutorialFragment.getEdgeAnimation();
+ if (!isGestureSuccessful && gestureAnimation != null && edgeAnimation != null) {
+ playFeedbackAnimation(
+ gestureAnimation,
+ edgeAnimation,
+ mShowFeedbackRunnable,
+ useGestureAnimationDelay);
+ return;
+ } else {
+ mTutorialFragment.releaseFeedbackAnimation();
}
- mFeedbackView.setTranslationY(-mFeedbackView.getHeight() - mFeedbackView.getTop());
- mFeedbackView.setVisibility(View.VISIBLE);
- mFeedbackView.animate()
- .setDuration(FEEDBACK_ANIMATION_MS)
- .translationY(0)
- .withEndAction(() -> {
- if (isGestureSuccessful && !mTutorialFragment.isAtFinalStep()) {
- if (mFeedbackViewCallback != null) {
- mFeedbackView.removeCallbacks(mFeedbackViewCallback);
- }
- mFeedbackViewCallback = mTutorialFragment::continueTutorial;
- mFeedbackView.postDelayed(mFeedbackViewCallback,
- ADVANCE_TUTORIAL_TIMEOUT_MS);
- }
- })
- .start();
- mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
+ mFeedbackViewCallback = mShowFeedbackRunnable;
+
+ mFeedbackView.post(mFeedbackViewCallback);
}
- void hideFeedback(boolean releaseFeedbackVideo) {
+ void hideFeedback() {
+ cancelQueuedGestureAnimation();
mFeedbackView.clearAnimation();
mFeedbackView.setVisibility(View.INVISIBLE);
- if (releaseFeedbackVideo) {
- mTutorialFragment.releaseFeedbackVideoView();
- }
}
- private void playFeedbackVideo(
- @NonNull AnimatedVectorDrawable tutorialAnimation,
- @NonNull AnimatedVectorDrawable gestureAnimation,
+ void cancelQueuedGestureAnimation() {
+ if (mFeedbackViewCallback != null) {
+ mFeedbackView.removeCallbacks(mFeedbackViewCallback);
+ mFeedbackViewCallback = null;
+ }
+ if (mFakeTaskViewCallback != null) {
+ mFakeTaskView.removeCallbacks(mFakeTaskViewCallback);
+ mFakeTaskViewCallback = null;
+ }
+ mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
+ }
+
+ private void playFeedbackAnimation(
+ @NonNull Animator gestureAnimation,
+ @NonNull AnimatedVectorDrawable edgeAnimation,
@NonNull Runnable onStartRunnable,
boolean useGestureAnimationDelay) {
- if (tutorialAnimation.isRunning()) {
- tutorialAnimation.reset();
+ if (gestureAnimation.isRunning()) {
+ gestureAnimation.cancel();
}
- tutorialAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() {
-
+ if (edgeAnimation.isRunning()) {
+ edgeAnimation.reset();
+ }
+ gestureAnimation.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Drawable drawable) {
- super.onAnimationStart(drawable);
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
- mGestureVideoView.setVisibility(GONE);
- if (gestureAnimation.isRunning()) {
- gestureAnimation.stop();
+ mEdgeGestureVideoView.setVisibility(GONE);
+ if (edgeAnimation.isRunning()) {
+ edgeAnimation.stop();
}
if (!useGestureAnimationDelay) {
@@ -305,37 +332,25 @@
}
@Override
- public void onAnimationEnd(Drawable drawable) {
- super.onAnimationEnd(drawable);
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
- mGestureVideoView.setVisibility(View.VISIBLE);
- gestureAnimation.start();
+ mEdgeGestureVideoView.setVisibility(View.VISIBLE);
+ edgeAnimation.start();
- tutorialAnimation.unregisterAnimationCallback(this);
+ gestureAnimation.removeListener(this);
}
});
- if (mFeedbackViewCallback != null) {
- mFeedbackVideoView.removeCallbacks(mFeedbackViewCallback);
- mFeedbackViewCallback = null;
- }
- if (mFeedbackVideoViewCallback != null) {
- mFeedbackVideoView.removeCallbacks(mFeedbackVideoViewCallback);
- mFeedbackVideoViewCallback = null;
- }
+ cancelQueuedGestureAnimation();
if (useGestureAnimationDelay) {
mFeedbackViewCallback = onStartRunnable;
- mFeedbackVideoViewCallback = () -> {
- mFeedbackVideoView.setVisibility(View.VISIBLE);
- tutorialAnimation.start();
- };
+ mFakeTaskViewCallback = gestureAnimation::start;
- mFeedbackVideoView.setVisibility(View.GONE);
mFeedbackView.post(mFeedbackViewCallback);
- mFeedbackVideoView.postDelayed(mFeedbackVideoViewCallback, GESTURE_ANIMATION_DELAY_MS);
+ mFakeTaskView.postDelayed(mFakeTaskViewCallback, GESTURE_ANIMATION_DELAY_MS);
} else {
- mFeedbackVideoView.setVisibility(View.VISIBLE);
- tutorialAnimation.start();
+ gestureAnimation.start();
}
}
@@ -360,7 +375,7 @@
@CallSuper
void transitToController() {
- hideFeedback(false);
+ hideFeedback();
hideActionButton();
updateSubtext();
updateDrawables();
@@ -395,6 +410,17 @@
mActionButton.setOnClickListener(this::onActionButtonClicked);
}
+ void updateFakeAppTaskViewLayout(@LayoutRes int mockAppTaskLayoutResId) {
+ mFakeTaskView.removeAllViews();
+ if (mockAppTaskLayoutResId != NO_ID) {
+ mFakeTaskView.addView(
+ inflate(mContext, mockAppTaskLayoutResId, null),
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+ }
+
private void updateSubtext() {
mTutorialStepView.setTutorialProgress(
mTutorialFragment.getCurrentStep(), mTutorialFragment.getNumSteps());
@@ -404,15 +430,12 @@
if (mContext != null) {
mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable(
mContext, getMockWallpaperResId()));
- mTutorialFragment.updateFeedbackVideo();
+ mTutorialFragment.updateFeedbackAnimation();
mFakeLauncherView.setBackgroundColor(
- mContext.getColor(Utilities.isDarkTheme(mContext)
- ? R.color.fake_wallpaper_color_dark_mode
- : R.color.fake_wallpaper_color_light_mode));
+ mContext.getColor(R.color.gesture_tutorial_fake_wallpaper_color));
mFakeHotseatView.setImageDrawable(AppCompatResources.getDrawable(
mContext, getMockHotseatResId()));
- mFakeTaskView.setBackground(AppCompatResources.getDrawable(
- mContext, getMockAppTaskThumbnailResId(Utilities.isDarkTheme(mContext))));
+ updateFakeAppTaskViewLayout(getMockAppTaskLayoutResId());
mFakeTaskView.animate().alpha(1).setListener(
AnimatorListeners.forSuccessCallback(() -> mFakeTaskView.animate().cancel()));
mFakePreviousTaskView.setBackground(AppCompatResources.getDrawable(
@@ -485,6 +508,52 @@
return null;
}
+ protected AnimatorSet createFingerDotAppearanceAnimatorSet() {
+ ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(
+ mFingerDotView, View.ALPHA, 0f, FINGER_DOT_VISIBLE_ALPHA);
+ ObjectAnimator yScaleAnimator = ObjectAnimator.ofFloat(
+ mFingerDotView, View.SCALE_Y, FINGER_DOT_SMALL_SCALE, 1f);
+ ObjectAnimator xScaleAnimator = ObjectAnimator.ofFloat(
+ mFingerDotView, View.SCALE_X, FINGER_DOT_SMALL_SCALE, 1f);
+ ArrayList<Animator> animators = new ArrayList<>();
+
+ animators.add(alphaAnimator);
+ animators.add(xScaleAnimator);
+ animators.add(yScaleAnimator);
+
+ AnimatorSet appearanceAnimatorSet = new AnimatorSet();
+
+ appearanceAnimatorSet.playTogether(animators);
+ appearanceAnimatorSet.setDuration(FINGER_DOT_ANIMATION_DURATION_MILLIS);
+
+ return appearanceAnimatorSet;
+ }
+
+ protected AnimatorSet createFingerDotDisappearanceAnimatorSet() {
+ ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(
+ mFingerDotView, View.ALPHA, FINGER_DOT_VISIBLE_ALPHA, 0f);
+ ObjectAnimator yScaleAnimator = ObjectAnimator.ofFloat(
+ mFingerDotView, View.SCALE_Y, 1f, FINGER_DOT_SMALL_SCALE);
+ ObjectAnimator xScaleAnimator = ObjectAnimator.ofFloat(
+ mFingerDotView, View.SCALE_X, 1f, FINGER_DOT_SMALL_SCALE);
+ ArrayList<Animator> animators = new ArrayList<>();
+
+ animators.add(alphaAnimator);
+ animators.add(xScaleAnimator);
+ animators.add(yScaleAnimator);
+
+ AnimatorSet appearanceAnimatorSet = new AnimatorSet();
+
+ appearanceAnimatorSet.playTogether(animators);
+ appearanceAnimatorSet.setDuration(FINGER_DOT_ANIMATION_DURATION_MILLIS);
+
+ return appearanceAnimatorSet;
+ }
+
+ protected Animator createAnimationPause() {
+ return ValueAnimator.ofFloat(0f, 1f).setDuration(GESTURE_ANIMATION_PAUSE_DURATION_MILLIS);
+ }
+
/** Denotes the type of the tutorial. */
enum TutorialType {
BACK_NAVIGATION,
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 7637450..52ec9b3 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.interaction;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -29,6 +31,7 @@
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.widget.ImageView;
@@ -38,7 +41,6 @@
import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.quickstep.interaction.TutorialController.TutorialType;
abstract class TutorialFragment extends Fragment implements OnTouchListener {
@@ -49,13 +51,14 @@
TutorialType mTutorialType;
@Nullable TutorialController mTutorialController = null;
RootSandboxLayout mRootView;
+ View mFingerDotView;
+ View mFakePreviousTaskView;
EdgeBackGestureHandler mEdgeBackGestureHandler;
NavBarGestureHandler mNavBarGestureHandler;
- private ImageView mFeedbackVideoView;
- private ImageView mGestureVideoView;
+ private ImageView mEdgeGestureVideoView;
- @Nullable private AnimatedVectorDrawable mTutorialAnimation = null;
- @Nullable private AnimatedVectorDrawable mGestureAnimation = null;
+ @Nullable private Animator mGestureAnimation = null;
+ @Nullable private AnimatedVectorDrawable mEdgeAnimation = null;
private boolean mIntroductionShown = false;
private boolean mFragmentStopped = false;
@@ -96,24 +99,26 @@
return null;
}
- @Nullable Integer getFeedbackVideoResId(boolean forDarkMode) {
- return null;
- }
-
- @Nullable Integer getGestureVideoResId() {
+ @Nullable Integer getEdgeAnimationResId() {
return null;
}
@Nullable
- AnimatedVectorDrawable getTutorialAnimation() {
- return mTutorialAnimation;
- }
-
- @Nullable
- AnimatedVectorDrawable getGestureAnimation() {
+ Animator getGestureAnimation() {
return mGestureAnimation;
}
+ @Nullable
+ AnimatedVectorDrawable getEdgeAnimation() {
+ return mEdgeAnimation;
+ }
+
+
+ @Nullable
+ protected Animator createGestureAnimation() {
+ return null;
+ }
+
abstract TutorialController createController(TutorialType type);
abstract Class<? extends TutorialController> getControllerClass();
@@ -147,21 +152,22 @@
return insets;
});
mRootView.setOnTouchListener(this);
- mFeedbackVideoView = mRootView.findViewById(R.id.gesture_tutorial_feedback_video);
- mGestureVideoView = mRootView.findViewById(R.id.gesture_tutorial_gesture_video);
+ mEdgeGestureVideoView = mRootView.findViewById(R.id.gesture_tutorial_edge_gesture_video);
+ mFingerDotView = mRootView.findViewById(R.id.gesture_tutorial_finger_dot);
+ mFakePreviousTaskView = mRootView.findViewById(
+ R.id.gesture_tutorial_fake_previous_task_view);
return mRootView;
}
@Override
public void onStop() {
super.onStop();
- releaseFeedbackVideoView();
- releaseGestureVideoView();
+ releaseFeedbackAnimation();
mFragmentStopped = true;
}
void initializeFeedbackVideoView() {
- if (!updateFeedbackVideo()) {
+ if (!updateFeedbackAnimation()) {
return;
}
@@ -176,87 +182,90 @@
}
}
- boolean updateFeedbackVideo() {
- if (getContext() == null) {
+ boolean updateFeedbackAnimation() {
+ if (!updateEdgeAnimation()) {
return false;
}
- Integer feedbackVideoResId = getFeedbackVideoResId(Utilities.isDarkTheme(getContext()));
-
- if (feedbackVideoResId == null || !updateGestureVideo()) {
- return false;
- }
- mTutorialAnimation = (AnimatedVectorDrawable) getContext().getDrawable(feedbackVideoResId);
-
- if (mTutorialAnimation != null) {
- mTutorialAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() {
-
- @Override
- public void onAnimationStart(Drawable drawable) {
- super.onAnimationStart(drawable);
-
- mFeedbackVideoView.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(Drawable drawable) {
- super.onAnimationEnd(drawable);
-
- releaseFeedbackVideoView();
- }
- });
- }
- mFeedbackVideoView.setImageDrawable(mTutorialAnimation);
-
- return true;
- }
-
- boolean updateGestureVideo() {
- Integer gestureVideoResId = getGestureVideoResId();
- if (gestureVideoResId == null || getContext() == null) {
- return false;
- }
- mGestureAnimation = (AnimatedVectorDrawable) getContext().getDrawable(gestureVideoResId);
+ mGestureAnimation = createGestureAnimation();
if (mGestureAnimation != null) {
- mGestureAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() {
+ mGestureAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mFingerDotView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ mFingerDotView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mFingerDotView.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ return mGestureAnimation != null;
+ }
+
+ boolean updateEdgeAnimation() {
+ Integer edgeAnimationResId = getEdgeAnimationResId();
+ if (edgeAnimationResId == null || getContext() == null) {
+ return false;
+ }
+ mEdgeAnimation = (AnimatedVectorDrawable) getContext().getDrawable(edgeAnimationResId);
+
+ if (mEdgeAnimation != null) {
+ mEdgeAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
- mGestureAnimation.start();
+ mEdgeAnimation.start();
}
});
}
- mGestureVideoView.setImageDrawable(mGestureAnimation);
+ mEdgeGestureVideoView.setImageDrawable(mEdgeAnimation);
- return true;
+ return mEdgeAnimation != null;
}
- void releaseFeedbackVideoView() {
- if (mTutorialAnimation != null && mTutorialAnimation.isRunning()) {
- mTutorialAnimation.stop();
+ void releaseFeedbackAnimation() {
+ if (mTutorialController != null) {
+ mTutorialController.cancelQueuedGestureAnimation();
}
-
- mFeedbackVideoView.setVisibility(View.GONE);
- }
-
- void releaseGestureVideoView() {
if (mGestureAnimation != null && mGestureAnimation.isRunning()) {
- mGestureAnimation.stop();
+ mGestureAnimation.cancel();
+ }
+ if (mEdgeAnimation != null && mEdgeAnimation.isRunning()) {
+ mEdgeAnimation.stop();
}
- mGestureVideoView.setVisibility(View.GONE);
+ mEdgeGestureVideoView.setVisibility(View.GONE);
}
@Override
public void onResume() {
super.onResume();
+ releaseFeedbackAnimation();
if (mFragmentStopped && mTutorialController != null) {
mTutorialController.showFeedback();
mFragmentStopped = false;
} else {
- changeController(mTutorialType);
+ mRootView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ changeController(mTutorialType);
+ mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
}
}
@@ -292,6 +301,7 @@
mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController);
mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController);
mTutorialType = tutorialType;
+
initializeFeedbackVideoView();
}
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index f1b4e3d..7c83833 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -48,15 +48,16 @@
public class AnimatorControllerWithResistance {
private enum RecentsResistanceParams {
- FROM_APP(0.75f, 0.5f, 1f),
- FROM_APP_TABLET(0.9f, 0.75f, 1f),
- FROM_OVERVIEW(1f, 0.75f, 0.5f);
+ FROM_APP(0.75f, 0.5f, 1f, false),
+ FROM_APP_TABLET(1f, 0.7f, 1f, true),
+ FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
- float translationFactor) {
+ float translationFactor, boolean stopScalingAtTop) {
this.scaleStartResist = scaleStartResist;
this.scaleMaxResist = scaleMaxResist;
this.translationFactor = translationFactor;
+ this.stopScalingAtTop = stopScalingAtTop;
}
/**
@@ -74,6 +75,12 @@
* where 0 will keep it centered and 1 will have it barely touch the top of the screen.
*/
public final float translationFactor;
+
+ /**
+ * Whether to end scaling effect when the scaled down version of TaskView's top reaches the
+ * non-scaled version of TaskView's top.
+ */
+ public final boolean stopScalingAtTop;
}
private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
@@ -151,8 +158,7 @@
Rect startRect = new Rect();
PagedOrientationHandler orientationHandler = params.recentsOrientedState
.getOrientationHandler();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
- orientationHandler);
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect);
long distanceToCover = startRect.bottom;
PendingAnimation resistAnim = params.resistAnim != null
? params.resistAnim
@@ -161,26 +167,6 @@
PointF pivot = new PointF();
float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
startRect, params.dp, pivot);
- float prevScaleRate = (fullscreenScale - params.startScale)
- / (params.dp.heightPx - startRect.bottom);
- // This is what the scale would be at the end of the drag if we didn't apply resistance.
- float endScale = params.startScale - prevScaleRate * distanceToCover;
- // Create an interpolator that resists the scale so the scale doesn't get smaller than
- // RECENTS_SCALE_MAX_RESIST.
- float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
- params.startScale, endScale);
- float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
- params.startScale, endScale);
- final TimeInterpolator scaleInterpolator = t -> {
- if (t < startResist) {
- return t;
- }
- float resistProgress = Utilities.getProgress(t, startResist, 1);
- resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
- return startResist + resistProgress * (maxResist - startResist);
- };
- resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
- scaleInterpolator);
// Compute where the task view would be based on the end scale.
RectF endRectF = new RectF(startRect);
@@ -195,6 +181,32 @@
resistAnim.addFloat(params.translationTarget, params.translationProperty,
params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
+ float prevScaleRate = (fullscreenScale - params.startScale)
+ / (params.dp.heightPx - startRect.bottom);
+ // This is what the scale would be at the end of the drag if we didn't apply resistance.
+ float endScale = params.startScale - prevScaleRate * distanceToCover;
+ // Create an interpolator that resists the scale so the scale doesn't get smaller than
+ // RECENTS_SCALE_MAX_RESIST.
+ float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
+ params.startScale, endScale);
+ float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
+ params.startScale, endScale);
+ float stopResist =
+ params.resistanceParams.stopScalingAtTop ? 1f - startRect.top / endRectF.top : 1f;
+ final TimeInterpolator scaleInterpolator = t -> {
+ if (t < startResist) {
+ return t;
+ }
+ if (t > stopResist) {
+ return maxResist;
+ }
+ float resistProgress = Utilities.getProgress(t, startResist, stopResist);
+ resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
+ return startResist + resistProgress * (maxResist - startResist);
+ };
+ resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
+ scaleInterpolator);
+
return resistAnim;
}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
new file mode 100644
index 0000000..fa4cddc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
@@ -0,0 +1,164 @@
+package com.android.quickstep.util;
+
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+
+import android.content.Context;
+import android.os.IBinder;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.launcher3.util.SplitConfigurationOptions.StageType;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+
+/**
+ * Listeners for system wide split screen position and stage changes.
+ *
+ * Use {@link #getRunningSplitTaskIds()} to determine which tasks, if any, are actively in
+ * staged split.
+ *
+ * Use {@link #getPersistentSplitIds()} to know if tasks were in split screen before a quickswitch
+ * gesture happened.
+ */
+public class LauncherSplitScreenListener extends ISplitScreenListener.Stub {
+
+ public static final MainThreadInitializedObject<LauncherSplitScreenListener> INSTANCE =
+ new MainThreadInitializedObject<>(LauncherSplitScreenListener::new);
+
+ private static final int[] EMPTY_ARRAY = {};
+
+ private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
+ private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
+
+ private boolean mIsRecentsListFrozen = false;
+ private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ @Override
+ public void onRecentTaskListFrozenChanged(boolean frozen) {
+ super.onRecentTaskListFrozenChanged(frozen);
+ mIsRecentsListFrozen = frozen;
+
+ if (frozen) {
+ mPersistentGroupedIds = getRunningSplitTaskIds();
+ } else {
+ mPersistentGroupedIds = EMPTY_ARRAY;
+ }
+ }
+ };
+
+ /**
+ * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
+ * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to
+ * home - in that case the task list does not unfreeze immediately after the gesture, so it's
+ * done via {@link #notifySwipingToHome()}.
+ *
+ * When not empty, this indicates that we need to load a GroupedTaskView as the most recent
+ * page, so user can quickswitch back to a grouped task.
+ */
+ private int[] mPersistentGroupedIds;
+
+ public LauncherSplitScreenListener(Context context) {
+ mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
+ mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
+ }
+
+ /** Also call {@link #destroy()} when done. */
+ public void init() {
+ SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+ }
+
+ public void destroy() {
+ SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ }
+
+ /**
+ * This method returns the active split taskIDs that were active if a user quickswitched from
+ * split screen to a fullscreen app as long as the recents task list remains frozen.
+ */
+ public int[] getPersistentSplitIds() {
+ if (mIsRecentsListFrozen) {
+ return mPersistentGroupedIds;
+ } else {
+ return getRunningSplitTaskIds();
+ }
+ }
+ /**
+ * @return index 0 will be task in left/top position, index 1 in right/bottom position.
+ * Will return empty array if device is not in staged split
+ */
+ public int[] getRunningSplitTaskIds() {
+ if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
+ return new int[]{};
+ }
+ int[] out = new int[2];
+ if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ out[0] = mMainStagePosition.taskId;
+ out[1] = mSideStagePosition.taskId;
+ } else {
+ out[1] = mMainStagePosition.taskId;
+ out[0] = mSideStagePosition.taskId;
+ }
+ return out;
+ }
+
+ @Override
+ public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+ mMainStagePosition.stagePosition = position;
+ } else {
+ mSideStagePosition.stagePosition = position;
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
+ // If task is not visible but we are tracking it, stop tracking it
+ if (!visible) {
+ if (mMainStagePosition.taskId == taskId) {
+ resetTaskId(mMainStagePosition);
+ } else if (mSideStagePosition.taskId == taskId) {
+ resetTaskId(mSideStagePosition);
+ } // else it's an un-tracked child
+ return;
+ }
+
+ // If stage has moved to undefined, stop tracking the task
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
+ resetTaskId(taskId == mMainStagePosition.taskId ?
+ mMainStagePosition : mSideStagePosition);
+ return;
+ }
+
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+ mMainStagePosition.taskId = taskId;
+ } else {
+ mSideStagePosition.taskId = taskId;
+ }
+ }
+
+ /** Notifies SystemUi to remove any split screen state */
+ public void notifySwipingToHome() {
+ boolean hasSplitTasks = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getPersistentSplitIds().length > 0;
+ if (!hasSplitTasks) {
+ return;
+ }
+
+ SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(-1);
+ mPersistentGroupedIds = EMPTY_ARRAY;
+ }
+
+ private void resetTaskId(StagedSplitTaskPosition taskPosition) {
+ taskPosition.taskId = -1;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
new file mode 100644
index 0000000..47d3580
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -0,0 +1,117 @@
+/*
+ * 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 static com.android.launcher3.Utilities.comp;
+
+import android.annotation.Nullable;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.util.HorizontalInsettableView;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+/**
+ * Controls animations that are happening during unfolding foldable devices
+ */
+public class LauncherUnfoldAnimationController {
+
+ // Percentage of the width of the quick search bar that will be reduced
+ // from the both sides of the bar when progress is 0
+ private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
+
+ private final Launcher mLauncher;
+
+ @Nullable
+ private HorizontalInsettableView mQsbInsettable;
+
+ private final ScopedUnfoldTransitionProgressProvider mProgressProvider;
+
+ public LauncherUnfoldAnimationController(
+ Launcher launcher,
+ WindowManager windowManager,
+ UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
+ mLauncher = launcher;
+ mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
+ unfoldTransitionProgressProvider);
+
+ mProgressProvider.addCallback(new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
+ windowManager));
+ mProgressProvider.addCallback(new QsbAnimationListener());
+ }
+
+ /**
+ * Called when launcher is resumed
+ */
+ public void onResume() {
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat != null && hotseat.getQsb() instanceof HorizontalInsettableView) {
+ mQsbInsettable = (HorizontalInsettableView) hotseat.getQsb();
+ }
+
+ final ViewTreeObserver obs = mLauncher.getWorkspace().getViewTreeObserver();
+ obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ if (obs.isAlive()) {
+ mProgressProvider.setReadyToHandleTransition(true);
+ obs.removeOnPreDrawListener(this);
+ }
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Called when launcher activity is paused
+ */
+ public void onPause() {
+ mProgressProvider.setReadyToHandleTransition(false);
+ mQsbInsettable = null;
+ }
+
+ /**
+ * Called when launcher activity is destroyed
+ */
+ public void onDestroy() {
+ mProgressProvider.destroy();
+ }
+
+ private class QsbAnimationListener implements TransitionProgressListener {
+
+ @Override
+ public void onTransitionStarted() {
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ if (mQsbInsettable != null) {
+ mQsbInsettable.setHorizontalInsets(0);
+ }
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ if (mQsbInsettable != null) {
+ float insetPercentage = comp(progress) * MAX_WIDTH_INSET_FRACTION;
+ mQsbInsettable.setHorizontalInsets(insetPercentage);
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
new file mode 100644
index 0000000..effdfdd
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
@@ -0,0 +1,45 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.view.View;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
+
+/**
+ * Class that allows to set translations for move from center animation independently
+ * from other translations for certain launcher views
+ */
+public class LauncherViewsMoveFromCenterTranslationApplier implements TranslationApplier {
+
+ @Override
+ public void apply(@NonNull View view, float x, float y) {
+ if (view instanceof NavigableAppWidgetHostView) {
+ ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
+ } else if (view instanceof BubbleTextView) {
+ ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
+ } else if (view instanceof FolderIcon) {
+ ((FolderIcon) view).setTranslationForMoveFromCenterAnimation(x, y);
+ } else {
+ view.setTranslationX(x);
+ view.setTranslationY(y);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 8834dc2..302526d 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -42,8 +42,7 @@
PagedOrientationHandler orientationHandler) {
// Track the bottom of the window.
Rect taskSize = new Rect();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(
- context, dp, taskSize, orientationHandler);
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize);
return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index ac2534e..b83e26e 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -119,9 +119,10 @@
* @param pointerIndex Index for the pointer being tracked in the motion event
*/
public void addPosition(MotionEvent ev, int pointerIndex) {
- mForcePauseTimeout.setAlarm(TestProtocol.sForcePauseTimeout != null
+ long timeoutMs = TestProtocol.sForcePauseTimeout != null
? TestProtocol.sForcePauseTimeout
- : mMakePauseHarderToTrigger ? HARDER_TRIGGER_TIMEOUT : FORCE_PAUSE_TIMEOUT);
+ : mMakePauseHarderToTrigger ? HARDER_TRIGGER_TIMEOUT : FORCE_PAUSE_TIMEOUT;
+ mForcePauseTimeout.setAlarm(timeoutMs);
float newVelocity = mVelocityProvider.addMotionEvent(ev, ev.getPointerId(pointerIndex));
if (mPreviousVelocity != null) {
checkMotionPaused(newVelocity, mPreviousVelocity, ev.getEventTime());
diff --git a/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
new file mode 100644
index 0000000..3777c65
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ProxyScreenStatusProvider.java
@@ -0,0 +1,51 @@
+/*
+ * 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);
+ }
+
+ @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/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 7cfd151..a4db596 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -396,9 +396,17 @@
Rect insets = dp.getInsets();
float fullWidth = dp.widthPx;
float fullHeight = dp.heightPx;
- if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
- fullWidth -= insets.left + insets.right;
- fullHeight -= insets.top + insets.bottom;
+ if (TaskView.clipLeft(dp)) {
+ fullWidth -= insets.left;
+ }
+ if (TaskView.clipRight(dp)) {
+ fullWidth -= insets.right;
+ }
+ if (TaskView.clipTop(dp)) {
+ fullHeight -= insets.top;
+ }
+ if (TaskView.clipBottom(dp)) {
+ fullHeight -= insets.bottom;
}
getTaskDimension(mContext, dp, outPivot);
@@ -592,17 +600,7 @@
width = Math.min(currentSize.x, currentSize.y);
height = Math.max(currentSize.x, currentSize.y);
}
-
- DeviceProfile bestMatch = idp.supportedProfiles.get(0);
- float minDiff = Float.MAX_VALUE;
- for (DeviceProfile profile : idp.supportedProfiles) {
- float diff = Math.abs(profile.widthPx - width) + Math.abs(profile.heightPx - height);
- if (diff < minDiff) {
- minDiff = diff;
- bestMatch = profile;
- }
- }
- return bestMatch;
+ return idp.getBestMatch(width, height);
}
private static String nameAndAddress(Object obj) {
diff --git a/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java b/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java
new file mode 100644
index 0000000..2ef311f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java
@@ -0,0 +1,140 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages progress listeners that can have smaller lifespan than the unfold animation.
+ * Allows to limit getting transition updates to only when
+ * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called
+ * with readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play
+ * the transition then it will report transition started callback and current animation progress.
+ */
+public final class ScopedUnfoldTransitionProgressProvider implements
+ UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+ private static final float PROGRESS_UNSET = -1f;
+
+ @Nullable
+ private UnfoldTransitionProgressProvider mSource;
+
+ private final List<TransitionProgressListener> mListeners = new ArrayList<>();
+
+ private boolean mIsReadyToHandleTransition;
+ private boolean mIsTransitionRunning;
+ private float mLastTransitionProgress = PROGRESS_UNSET;
+
+ public ScopedUnfoldTransitionProgressProvider() {
+ this(null);
+ }
+
+ public ScopedUnfoldTransitionProgressProvider(@Nullable UnfoldTransitionProgressProvider
+ source) {
+ setSourceProvider(source);
+ }
+
+ /**
+ * Sets the source for the unfold transition progress updates,
+ * it replaces current provider if it is already set
+ * @param provider transition provider that emits transition progress updates
+ */
+ public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) {
+ if (mSource != null) {
+ mSource.removeCallback(this);
+ }
+
+ if (provider != null) {
+ mSource = provider;
+ mSource.addCallback(this);
+ }
+ }
+
+ /**
+ * Allows to notify this provide whether the listeners can play the transition or not.
+ * Call this method with readyToHandleTransition = true when all listeners
+ * are ready to consume the transition progress events.
+ * Call it with readyToHandleTransition = false when listeners can't process the events.
+ */
+ public void setReadyToHandleTransition(boolean isReadyToHandleTransition) {
+ if (mIsTransitionRunning) {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+
+ if (mLastTransitionProgress != PROGRESS_UNSET) {
+ mListeners.forEach(listener ->
+ listener.onTransitionProgress(mLastTransitionProgress));
+ }
+ } else {
+ mIsTransitionRunning = false;
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+ }
+
+ mIsReadyToHandleTransition = isReadyToHandleTransition;
+ }
+
+ @Override
+ public void addCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void destroy() {
+ mSource.removeCallback(this);
+ }
+
+ @Override
+ public void onTransitionStarted() {
+ this.mIsTransitionRunning = true;
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+ }
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(listener -> listener.onTransitionProgress(progress));
+ }
+
+ mLastTransitionProgress = progress;
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+
+ mIsTransitionRunning = false;
+ mLastTransitionProgress = PROGRESS_UNSET;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 16c925a..3069504 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -16,38 +16,31 @@
package com.android.quickstep.util;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import android.app.ActivityThread;
-import android.content.res.Resources;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.IBinder;
-import android.view.Gravity;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.R;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskViewUtils;
-import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.RemoteTransitionCompat;
import com.android.systemui.shared.system.RemoteTransitionRunner;
+import java.util.function.Consumer;
+
/**
* Represent data needed for the transient state when user has selected one app for split screen
* and is in the process of either a) selecting a second app or b) exiting intention to invoke split
@@ -55,46 +48,51 @@
public class SplitSelectStateController {
private final SystemUiProxy mSystemUiProxy;
- private TaskView mInitialTaskView;
- private TaskView mSecondTaskView;
- private SplitPositionOption mInitialPosition;
+ private @StagePosition int mStagePosition;
+ private Task mInitialTask;
+ private Task mSecondTask;
private Rect mInitialBounds;
- private final Handler mHandler;
- public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy) {
+ public SplitSelectStateController(SystemUiProxy systemUiProxy) {
mSystemUiProxy = systemUiProxy;
- mHandler = handler;
}
/**
* To be called after first task selected
*/
- public void setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption,
+ public void setInitialTaskSelect(Task taskView, @StagePosition int stagePosition,
Rect initialBounds) {
- mInitialTaskView = taskView;
- mInitialPosition = positionOption;
+ mInitialTask = taskView;
+ mStagePosition = stagePosition;
mInitialBounds = initialBounds;
}
/**
* To be called after second task selected
*/
- public void setSecondTaskId(TaskView taskView) {
- mSecondTaskView = taskView;
- // Assume initial task is for top/left part of screen
+ public void setSecondTaskId(Task taskView) {
+ mSecondTask = taskView;
+ launchTasks(mInitialTask, mSecondTask, mStagePosition, null /*callback*/);
+ }
- final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
- ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
- : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+ /**
+ * @param stagePosition representing location of task1
+ */
+ public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition,
+ Consumer<Boolean> callback) {
+ // Assume initial task is for top/left part of screen
+ final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? new int[]{task1.key.id, task2.key.id}
+ : new int[]{task2.key.id, task1.key.id};
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
RemoteSplitLaunchTransitionRunner animationRunner =
- new RemoteSplitLaunchTransitionRunner(mInitialTaskView, taskView);
+ new RemoteSplitLaunchTransitionRunner(task1, task2);
mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR));
} else {
RemoteSplitLaunchAnimationRunner animationRunner =
- new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView);
+ new RemoteSplitLaunchAnimationRunner(task1, task2, callback);
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),
300, 150,
@@ -105,29 +103,8 @@
}
}
- /**
- * @return {@link InsettableFrameLayout.LayoutParams} to correctly position the
- * split placeholder view
- */
- public InsettableFrameLayout.LayoutParams getLayoutParamsForActivePosition(Resources resources,
- DeviceProfile deviceProfile) {
- InsettableFrameLayout.LayoutParams params =
- new InsettableFrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
- boolean topLeftPosition = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT;
- if (deviceProfile.isLandscape) {
- params.width = (int) resources.getDimension(R.dimen.split_placeholder_size);
- params.gravity = topLeftPosition ? Gravity.START : Gravity.END;
- } else {
- params.height = (int) resources.getDimension(R.dimen.split_placeholder_size);
- params.gravity = Gravity.TOP;
- }
-
- return params;
- }
-
- @Nullable
- public SplitPositionOption getActiveSplitPositionOption() {
- return mInitialPosition;
+ public @StagePosition int getActiveSplitStagePosition() {
+ return mStagePosition;
}
/**
@@ -135,19 +112,19 @@
*/
private class RemoteSplitLaunchTransitionRunner implements RemoteTransitionRunner {
- private final TaskView mInitialTaskView;
- private final TaskView mTaskView;
+ private final Task mInitialTask;
+ private final Task mSecondTask;
- RemoteSplitLaunchTransitionRunner(TaskView initialTaskView, TaskView taskView) {
- mInitialTaskView = initialTaskView;
- mTaskView = taskView;
+ RemoteSplitLaunchTransitionRunner(Task initialTask, Task secondTask) {
+ mInitialTask = initialTask;
+ mSecondTask = secondTask;
}
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, Runnable finishCallback) {
- TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTaskView, mTaskView,
- info, t, finishCallback);
+ TaskViewUtils.composeRecentsSplitLaunchAnimator(mInitialTask,
+ mSecondTask, info, t, finishCallback);
// After successful launch, call resetState
resetState();
}
@@ -159,26 +136,37 @@
*/
private class RemoteSplitLaunchAnimationRunner implements RemoteAnimationRunnerCompat {
- private final TaskView mInitialTaskView;
- private final TaskView mTaskView;
+ private final Task mInitialTask;
+ private final Task mSecondTask;
+ private final Consumer<Boolean> mSuccessCallback;
- RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView) {
- mInitialTaskView = initialTaskView;
- mTaskView = taskView;
+ RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask,
+ Consumer<Boolean> successCallback) {
+ mInitialTask = initialTask;
+ mSecondTask = secondTask;
+ mSuccessCallback = successCallback;
}
@Override
public void onAnimationStart(int transit, RemoteAnimationTargetCompat[] apps,
RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
Runnable finishedCallback) {
- TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(mInitialTaskView, mTaskView, apps,
- wallpapers, nonApps, finishedCallback);
+ TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(mInitialTask,
+ mSecondTask, apps, wallpapers, nonApps, () -> {
+ finishedCallback.run();
+ if (mSuccessCallback != null) {
+ mSuccessCallback.accept(true);
+ }
+ });
// After successful launch, call resetState
resetState();
}
@Override
public void onAnimationCancelled() {
+ if (mSuccessCallback != null) {
+ mSuccessCallback.accept(false);
+ }
resetState();
}
}
@@ -187,9 +175,9 @@
* To be called if split select was cancelled
*/
public void resetState() {
- mInitialTaskView = null;
- mSecondTaskView = null;
- mInitialPosition = null;
+ mInitialTask = null;
+ mSecondTask = null;
+ mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
mInitialBounds = null;
}
@@ -198,7 +186,7 @@
* chosen
*/
public boolean isSplitSelectActive() {
- return mInitialTaskView != null && mSecondTaskView == null;
+ return mInitialTask != null && mSecondTask == null;
}
public Rect getInitialBounds() {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 7eee415..849a7bc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.states.RotationHelper.deltaRotation;
import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
+import static com.android.launcher3.util.SplitConfigurationOptions.*;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
@@ -29,6 +30,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Log;
import androidx.annotation.NonNull;
@@ -49,8 +51,13 @@
*/
public class TaskViewSimulator implements TransformParams.BuilderProxy {
+ private static final String TAG = "TaskViewSimulator";
+ private static final boolean DEBUG = false;
+
private final Rect mTmpCropRect = new Rect();
private final RectF mTempRectF = new RectF();
+ // Additional offset for split tasks
+ private final Point mSplitOffset = new Point();
private final float[] mTempPoint = new float[2];
private final Context mContext;
@@ -63,6 +70,8 @@
private final Rect mTaskRect = new Rect();
private final PointF mPivot = new PointF();
private DeviceProfile mDp;
+ @StagePosition
+ private int mStagePosition = STAGE_POSITION_UNDEFINED;
private final Matrix mMatrix = new Matrix();
private final Matrix mMatrixTmp = new Matrix();
@@ -89,6 +98,7 @@
// Cached calculations
private boolean mLayoutValid = false;
private int mOrientationStateId;
+ private StagedSplitBounds mStagedSplitBounds;
public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
mContext = context;
@@ -128,9 +138,20 @@
if (mDp == null) {
return 1;
}
- mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
- mOrientationState.getOrientationHandler());
- return mOrientationState.getFullScreenScaleAndPivot(mTaskRect, mDp, mPivot);
+ Rect fullTaskSize = new Rect();
+ mSizeStrategy.calculateTaskSize(mContext, mDp, fullTaskSize);
+
+ if (mStagedSplitBounds != null) {
+ // The task rect changes according to the staged split task sizes, but recents
+ // fullscreen scale and pivot remains the same since the task fits into the existing
+ // sized task space bounds
+ mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect);
+ mOrientationState.getOrientationHandler()
+ .setSplitTaskSwipeRect(mDp, mTaskRect, mStagedSplitBounds, mStagePosition);
+ } else {
+ mTaskRect.set(fullTaskSize);
+ }
+ return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
}
/**
@@ -143,6 +164,24 @@
}
/**
+ * Sets the targets which the simulator will control specifically for targets to animate when
+ * in split screen
+ *
+ * @param splitInfo set to {@code null} when not in staged split mode
+ */
+ public void setPreview(RemoteAnimationTargetCompat runningTarget, StagedSplitBounds splitInfo) {
+ setPreview(runningTarget);
+ mStagedSplitBounds = splitInfo;
+ if (mStagedSplitBounds == null) {
+ mStagePosition = STAGE_POSITION_UNDEFINED;
+ return;
+ }
+ mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds) ?
+ STAGE_POSITION_TOP_OR_LEFT :
+ STAGE_POSITION_BOTTOM_OR_RIGHT;
+ }
+
+ /**
* Sets the targets which the simulator will control
*/
public void setPreviewBounds(Rect bounds, Rect insets) {
@@ -239,6 +278,11 @@
getFullScreenScale();
mThumbnailData.rotation = mOrientationState.getDisplayRotation();
+ if (mStagedSplitBounds != null) {
+ mOrientationState.getOrientationHandler().setLeashSplitOffset(mSplitOffset, mDp,
+ mStagedSplitBounds, mStagePosition);
+ }
+
// mIsRecentsRtl is the inverse of TaskView RTL.
boolean isRtlEnabled = !mIsRecentsRtl;
mPositionHelper.updateThumbnailMatrix(
@@ -246,12 +290,14 @@
mTaskRect.width(), mTaskRect.height(),
mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
mPositionHelper.getMatrix().invert(mInversePositionMatrix);
+ if (DEBUG) {
+ Log.d(TAG, " taskRect: " + mTaskRect + " splitOffset: " + mSplitOffset);
+ }
}
float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
- mCurrentFullscreenParams.setProgress(
- fullScreenProgress, recentsViewScale.value, /*taskViewScale=*/1f, mTaskRect.width(),
- mDp, mPositionHelper);
+ mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
+ /* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
// Apply thumbnail matrix
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
@@ -280,6 +326,9 @@
recentsViewPrimaryTranslation.value);
applyWindowToHomeRotation(mMatrix);
+ // Move lower/right split window into correct position
+ mMatrix.postTranslate(mSplitOffset.x, mSplitOffset.y);
+
// Crop rect is the inverse of thumbnail matrix
mTempRectF.set(-insets.left, -insets.top,
taskWidth + insets.right, taskHeight + insets.bottom);
@@ -287,6 +336,25 @@
mTempRectF.roundOut(mTmpCropRect);
params.applySurfaceParams(params.createSurfaceParams(this));
+
+ if (!DEBUG) {
+ return;
+ }
+ Log.d(TAG, "progress: " + fullScreenProgress
+ + " scale: " + scale
+ + " recentsViewScale: " + recentsViewScale.value
+ + " crop: " + mTmpCropRect
+ + " radius: " + getCurrentCornerRadius()
+ + " translate: " + mSplitOffset
+ + " taskW: " + taskWidth + " H: " + taskHeight
+ + " taskRect: " + mTaskRect
+ + " taskPrimaryT: " + taskPrimaryTranslation.value
+ + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
+ + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
+ + " taskSecondaryT: " + taskSecondaryTranslation.value
+ + " recentsScroll: " + recentsViewScroll.value
+ + " pivot: " + mPivot
+ );
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
new file mode 100644
index 0000000..95403b2
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -0,0 +1,121 @@
+/*
+ * 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 android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Workspace;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Animation that moves launcher icons and widgets from center to the sides (final position)
+ */
+public class UnfoldMoveFromCenterWorkspaceAnimator
+ implements UnfoldTransitionProgressProvider.TransitionProgressListener {
+
+ private final Launcher mLauncher;
+ private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation;
+
+ private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
+ private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
+
+ public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
+ mLauncher = launcher;
+ mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
+ new LauncherViewsMoveFromCenterTranslationApplier());
+ }
+
+ @Override
+ public void onTransitionStarted() {
+ mMoveFromCenterAnimation.updateDisplayProperties();
+
+ Workspace workspace = mLauncher.getWorkspace();
+ Hotseat hotseat = mLauncher.getHotseat();
+
+ // App icons and widgets
+ workspace
+ .forEachVisiblePage(page -> {
+ final CellLayout cellLayout = (CellLayout) page;
+ ShortcutAndWidgetContainer itemsContainer = cellLayout
+ .getShortcutsAndWidgets();
+ disableClipping(cellLayout);
+
+ for (int i = 0; i < itemsContainer.getChildCount(); i++) {
+ View child = itemsContainer.getChildAt(i);
+ mMoveFromCenterAnimation.registerViewForAnimation(child);
+ }
+ });
+
+ disableClipping(workspace);
+
+ // Hotseat icons
+ ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
+ disableClipping(hotseat);
+
+ for (int i = 0; i < hotseatIcons.getChildCount(); i++) {
+ View child = hotseatIcons.getChildAt(i);
+ mMoveFromCenterAnimation.registerViewForAnimation(child);
+ }
+
+ onTransitionProgress(0f);
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ mMoveFromCenterAnimation.onTransitionProgress(progress);
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ mMoveFromCenterAnimation.onTransitionFinished();
+ mMoveFromCenterAnimation.clearRegisteredViews();
+
+ restoreClipping(mLauncher.getWorkspace());
+ mLauncher.getWorkspace().forEachVisiblePage(page -> restoreClipping((CellLayout) page));
+ restoreClipping(mLauncher.getHotseat());
+
+ mOriginalClipChildren.clear();
+ mOriginalClipToPadding.clear();
+ }
+
+ private void disableClipping(ViewGroup view) {
+ mOriginalClipToPadding.put(view, view.getClipToPadding());
+ mOriginalClipChildren.put(view, view.getClipChildren());
+ view.setClipToPadding(false);
+ view.setClipChildren(false);
+ }
+
+ private void restoreClipping(ViewGroup view) {
+ final Boolean originalClipToPadding = mOriginalClipToPadding.get(view);
+ if (originalClipToPadding != null) {
+ view.setClipToPadding(originalClipToPadding);
+ }
+ final Boolean originalClipChildren = mOriginalClipChildren.get(view);
+ if (originalClipChildren != null) {
+ view.setClipChildren(originalClipChildren);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
similarity index 65%
rename from src/com/android/launcher3/util/VibratorWrapper.java
rename to quickstep/src/com/android/quickstep/util/VibratorWrapper.java
index b0defd4..211bd08 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.quickstep.util;
import static android.os.VibrationEffect.createPredefined;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
@@ -21,15 +21,20 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.media.AudioAttributes;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
*/
@@ -39,8 +44,15 @@
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
new MainThreadInitializedObject<>(VibratorWrapper::new);
+ public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build();
+
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
+ public static final VibrationEffect EFFECT_TEXTURE_TICK =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK);
/**
* Haptic when entering overview.
@@ -78,7 +90,27 @@
/** Vibrates with the given effect if haptic feedback is available and enabled. */
public void vibrate(VibrationEffect vibrationEffect) {
if (mHasVibrator && mIsHapticFeedbackEnabled) {
- UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+ UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS));
+ }
+ }
+
+ /**
+ * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
+ * vibrates if haptic feedback is available and enabled.
+ */
+ @SuppressLint("NewApi")
+ public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) {
+ if (mHasVibrator && mIsHapticFeedbackEnabled) {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (Utilities.ATLEAST_R && primitiveId >= 0
+ && mVibrator.areAllPrimitivesSupported(primitiveId)) {
+ mVibrator.vibrate(VibrationEffect.startComposition()
+ .addPrimitive(primitiveId, primitiveScale)
+ .compose(), VIBRATION_ATTRS);
+ } else {
+ mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS);
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index b9a9006..22c87b0 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -39,10 +39,24 @@
}
};
+ public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
+ new FloatProperty<ClearAllButton>("dismissAlpha") {
+ @Override
+ public Float get(ClearAllButton view) {
+ return view.mDismissAlpha;
+ }
+
+ @Override
+ public void setValue(ClearAllButton view, float v) {
+ view.setDismissAlpha(v);
+ }
+ };
+
private final StatefulActivity mActivity;
private float mScrollAlpha = 1;
private float mContentAlpha = 1;
private float mVisibilityAlpha = 1;
+ private float mDismissAlpha = 1;
private float mFullscreenProgress = 1;
private float mGridProgress = 1;
@@ -97,6 +111,13 @@
}
}
+ public void setDismissAlpha(float alpha) {
+ if (mDismissAlpha != alpha) {
+ mDismissAlpha = alpha;
+ updateAlpha();
+ }
+ }
+
public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
RecentsView recentsView = getRecentsView();
if (recentsView == null) {
@@ -123,7 +144,7 @@
}
private void updateAlpha() {
- final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha;
+ final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
setAlpha(alpha);
setClickable(Math.min(alpha, 1) == 1);
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index a1befc5..5a86464 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -97,8 +97,9 @@
floatingView.mOrientationHandler =
originalView.getRecentsView().getPagedOrientationHandler();
- floatingView.mSplitPlaceholderView.setIcon(originalView.getIconView());
- floatingView.mSplitPlaceholderView.getIcon()
+ floatingView.mSplitPlaceholderView.setIconView(originalView.getIconView(),
+ launcher.getDeviceProfile().overviewTaskIconDrawableSizePx);
+ floatingView.mSplitPlaceholderView.getIconView()
.setRotation(floatingView.mOrientationHandler.getDegreesRotated());
parent.addView(floatingView);
return floatingView;
@@ -141,8 +142,8 @@
// TODO(194414938) seems like this scale value could be fine tuned, some stretchiness
mImageView.setScaleX(1f / scaleX + scaleX * progress);
mImageView.setScaleY(1f / scaleY + scaleY * progress);
- mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIcon(), childScaleX);
- mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIcon(), childScaleY);
+ mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIconView(), childScaleX);
+ mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIconView(), childScaleY);
}
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
new file mode 100644
index 0000000..8562719
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -0,0 +1,143 @@
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.function.Consumer;
+
+/**
+ * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
+ *
+ * That's right. If you call within the next 5 minutes we'll go ahead and double your order and
+ * send you !! TWO !! Tasks along with their TaskThumbnailViews complimentary. On. The. House.
+ * And not only that, we'll even clean up your thumbnail request if you don't like it.
+ * All the benefits of one TaskView, except DOUBLED!
+ *
+ * (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
+ */
+public class GroupedTaskView extends TaskView {
+
+ private Task mSecondaryTask;
+ private TaskThumbnailView mSnapshotView2;
+ private CancellableTask mThumbnailLoadRequest2;
+
+ public GroupedTaskView(Context context) {
+ super(context);
+ }
+
+ public GroupedTaskView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSnapshotView2 = findViewById(R.id.bottomright_snapshot);
+ }
+
+ public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
+ SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig) {
+ super.bind(primary, orientedState);
+ mSecondaryTask = secondary;
+ mTaskIdContainer[1] = secondary.key.id;
+ mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2);
+ mSnapshotView2.bind(secondary);
+ adjustThumbnailBoundsForSplit(splitBoundsConfig, orientedState);
+ }
+
+ @Override
+ public void onTaskListVisibilityChanged(boolean visible, int changes) {
+ super.onTaskListVisibilityChanged(visible, changes);
+ if (visible) {
+ RecentsModel model = RecentsModel.INSTANCE.get(getContext());
+ TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
+
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(mSecondaryTask,
+ thumbnailData -> mSnapshotView2.setThumbnail(
+ mSecondaryTask, thumbnailData
+ ));
+ }
+
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ // TODO What's the Icon for this going to look like? :o
+ }
+ } else {
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ mSnapshotView2.setThumbnail(null, null);
+ // Reset the task thumbnail reference as well (it will be fetched from the cache or
+ // reloaded next time we need it)
+ mSecondaryTask.thumbnail = null;
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ // TODO
+ }
+ }
+ }
+
+ @Override
+ protected void cancelPendingLoadTasks() {
+ super.cancelPendingLoadTasks();
+ if (mThumbnailLoadRequest2 != null) {
+ mThumbnailLoadRequest2.cancel();
+ mThumbnailLoadRequest2 = null;
+ }
+ }
+
+ @Override
+ public RunnableList launchTaskAnimated() {
+ getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, null /*callback*/);
+ return null;
+ }
+
+ @Override
+ public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+ getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, callback);
+ }
+
+ @Override
+ public TaskThumbnailView[] getThumbnails() {
+ return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2};
+ }
+
+ @Override
+ public void onRecycle() {
+ super.onRecycle();
+ mSnapshotView2.setThumbnail(mSecondaryTask, null);
+ }
+
+ @Override
+ public void setOverlayEnabled(boolean overlayEnabled) {
+ super.setOverlayEnabled(overlayEnabled);
+ mSnapshotView2.setOverlayEnabled(overlayEnabled);
+ }
+
+ private void adjustThumbnailBoundsForSplit(
+ SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
+ RecentsOrientedState orientedState) {
+ if (splitBoundsConfig == null) {
+ return;
+ }
+
+ orientedState.getOrientationHandler().setGroupedTaskViewThumbnailBounds(
+ mSnapshotView, mSnapshotView2, this, splitBoundsConfig,
+ mActivity.getDeviceProfile());
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 6b2d19c..ddb1fca 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -245,8 +245,8 @@
@Override
public void initiateSplitSelect(TaskView taskView,
- SplitConfigurationOptions.SplitPositionOption splitPositionOption) {
- super.initiateSplitSelect(taskView, splitPositionOption);
+ @SplitConfigurationOptions.StagePosition int stagePosition) {
+ super.initiateSplitSelect(taskView, stagePosition);
mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index a2d2179..ac779b1 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -53,13 +53,15 @@
@IntDef(flag = true, value = {
HIDDEN_NON_ZERO_ROTATION,
HIDDEN_NO_TASKS,
- HIDDEN_NO_RECENTS})
+ HIDDEN_NO_RECENTS,
+ HIDDEN_FOCUSED_SCROLL})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionsHiddenFlags { }
public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0;
public static final int HIDDEN_NO_TASKS = 1 << 1;
public static final int HIDDEN_NO_RECENTS = 1 << 2;
+ public static final int HIDDEN_FOCUSED_SCROLL = 1 << 3;
@IntDef(flag = true, value = {
DISABLED_SCROLLING,
@@ -76,9 +78,9 @@
private static final int INDEX_VISIBILITY_ALPHA = 1;
private static final int INDEX_FULLSCREEN_ALPHA = 2;
private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
- private static final int INDEX_SCROLL_ALPHA = 4;
private final MultiValueAlpha mMultiValueAlpha;
+ private View mSplitButton;
@ActionsHiddenFlags
private int mHiddenFlags;
@@ -110,6 +112,8 @@
View share = findViewById(R.id.action_share);
share.setOnClickListener(this);
findViewById(R.id.action_screenshot).setOnClickListener(this);
+ mSplitButton = findViewById(R.id.action_split);
+ mSplitButton.setOnClickListener(this);
if (ENABLE_OVERVIEW_SHARE.get()) {
share.setVisibility(VISIBLE);
findViewById(R.id.oav_three_button_space).setVisibility(VISIBLE);
@@ -135,6 +139,8 @@
mCallbacks.onShare();
} else if (id == R.id.action_screenshot) {
mCallbacks.onScreenshot();
+ } else if (id == R.id.action_split) {
+ mCallbacks.onSplit();
}
}
@@ -191,10 +197,6 @@
return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
}
- public AlphaProperty getScrollAlpha() {
- return mMultiValueAlpha.getProperty(INDEX_SCROLL_ALPHA);
- }
-
private void updateHorizontalPadding() {
setPadding(mInsets.left, 0, mInsets.right, 0);
}
@@ -220,6 +222,15 @@
requestLayout();
}
+ public void setSplitButtonVisible(boolean visible) {
+ if (mSplitButton == null) {
+ return;
+ }
+
+ mSplitButton.setVisibility(visible ? VISIBLE : GONE);
+ findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE);
+ }
+
/** Get the top margin associated with the action buttons in Overview. */
public static int getOverviewActionsTopMarginPx(
SysUINavigationMode.Mode mode, DeviceProfile dp) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b077ca6..dd470e8 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,6 +35,7 @@
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.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.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -42,11 +43,13 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.testing.TestProtocol.TASK_VIEW_ID_CRASH;
import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
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;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
@@ -76,12 +79,15 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.VibrationEffect;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -123,12 +129,13 @@
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TranslateEdgeEffect;
import com.android.launcher3.util.ViewPool;
@@ -140,11 +147,14 @@
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.RemoteTargetGluer;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitScreenBounds;
@@ -152,6 +162,7 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -236,6 +247,12 @@
}
};
+ public static final int SCROLL_VIBRATION_PRIMITIVE =
+ Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
+ public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
+ public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
+ VibratorWrapper.EFFECT_TEXTURE_TICK;
+
/**
* Can be used to tint the color of the RecentsView to simulate a scrim that can views
* excluded from. Really should be a proper scrim.
@@ -314,7 +331,13 @@
view.setScaleY(scale);
view.mLastComputedTaskStartPushOutDistance = null;
view.mLastComputedTaskEndPushOutDistance = null;
- view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
+ view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() {
+ @Override
+ public void accept(RemoteTargetHandle remoteTargetHandle) {
+ remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value =
+ scale;
+ }
+ });
view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation);
view.updatePageOffsets();
}
@@ -346,6 +369,7 @@
private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
+ private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f;
private static final float SIGNIFICANT_MOVE_THRESHOLD_TABLET = 0.15f;
@@ -362,8 +386,7 @@
// mTaskGridVerticalDiff and mTopBottomRowHeightDiff summed together provides the top
// position for bottom row of grid tasks.
- protected final TransformParams mLiveTileParams = new TransformParams();
- protected final TaskViewSimulator mLiveTileTaskViewSimulator;
+ protected RemoteTargetHandle[] mRemoteTargetHandles;
protected final Rect mLastComputedTaskSize = new Rect();
protected final Rect mLastComputedGridSize = new Rect();
protected final Rect mLastComputedGridTaskSize = new Rect();
@@ -383,6 +406,7 @@
protected final ACTIVITY_TYPE mActivity;
private final float mFastFlingVelocity;
+ private final int mScrollHapticMinGapMillis;
private final RecentsModel mModel;
private final int mGridSideMargin;
private final ClearAllButton mClearAllButton;
@@ -399,9 +423,10 @@
private final InvariantDeviceProfile mIdp;
/**
- * Getting views should be done via {@link #getTaskViewFromPool()}
+ * Getting views should be done via {@link #getTaskViewFromPool(boolean)}
*/
private final ViewPool<TaskView> mTaskViewPool;
+ private final ViewPool<GroupedTaskView> mGroupedTaskViewPool;
private final TaskOverlayFactory mTaskOverlayFactory;
@@ -428,6 +453,7 @@
private ObjectAnimator mTintingAnimator;
private int mOverScrollShift = 0;
+ private long mScrollLastHapticTimestamp;
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -501,13 +527,13 @@
// Only valid until the launcher state changes to NORMAL
/**
* ID for the current running TaskView view, unique amongst TaskView instances. ID's are set
- * through {@link #getTaskViewFromPool()} and incremented by {@link #mTaskViewIdCount}
+ * through {@link #getTaskViewFromPool(boolean)} and incremented by {@link #mTaskViewIdCount}
*/
protected int mRunningTaskViewId = -1;
private int mTaskViewIdCount;
private final int[] INVALID_TASK_IDS = new int[]{-1, -1};
protected boolean mRunningTaskTileHidden;
- private Task mTmpRunningTask;
+ private Task[] mTmpRunningTasks;
protected int mFocusedTaskViewId = -1;
private boolean mTaskIconScaledDown = false;
@@ -562,6 +588,7 @@
*/
private TaskView mSplitHiddenTaskView;
private TaskView mSecondSplitHiddenTaskView;
+ private SplitConfigurationOptions.StagedSplitBounds mSplitBoundsConfig;
/**
* Keeps track of the index of the TaskView that split screen was initialized with so we know
@@ -600,6 +627,7 @@
};
private RunnableList mSideTaskLaunchCallback;
+ private TaskLaunchListener mTaskLaunchListener;
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
BaseActivityInterface sizeStrategy) {
@@ -612,6 +640,8 @@
final int rotation = mActivity.getDisplay().getRotation();
mOrientationState.setRecentsRotation(rotation);
+ mScrollHapticMinGapMillis = getResources()
+ .getInteger(R.integer.recentsScrollHapticMinGapMillis);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mModel = RecentsModel.INSTANCE.get(context);
@@ -622,6 +652,9 @@
mClearAllButton.setOnClickListener(this::dismissAllTasks);
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
10 /* initial size */);
+ // There's only one pair of grouped tasks we can envision at the moment
+ mGroupedTaskViewPool = new ViewPool<>(context, this,
+ R.layout.task_grouped, 2 /* max size */, 1 /* initial size */);
mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
@@ -652,10 +685,6 @@
// Initialize quickstep specific cache params here, as this is constructed only once
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
- mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
- mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
- mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
-
mTintingColor = getForegroundScrimDimColor(context);
}
@@ -703,7 +732,7 @@
super.dispatchDraw(canvas);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
- && mLiveTileParams.getTargetSet() != null) {
+ && mRemoteTargetHandles != null) {
redrawLiveTile();
}
}
@@ -745,9 +774,13 @@
if (mHandleTaskStackChanges) {
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
- Task task = taskView.getTask();
- taskView.getThumbnail().setThumbnail(task, thumbnailData);
- return task;
+ for (TaskView.TaskIdAttributeContainer container :
+ taskView.getTaskIdAttributeContainers()) {
+ if (container == null || taskId != container.getTask().key.id) {
+ continue;
+ }
+ container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);
+ }
}
}
return null;
@@ -808,7 +841,8 @@
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = new SurfaceTransactionApplier(this);
- mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
+ .setSyncTransactionApplier(mSyncTransactionApplier));
RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
mIPipAnimationListener.setActivityAndRecentsView(mActivity, this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
@@ -826,7 +860,8 @@
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
mSyncTransactionApplier = null;
- mLiveTileParams.setSyncTransactionApplier(null);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
+ .setSyncTransactionApplier(null));
executeSideTaskLaunchCallback();
RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
@@ -847,9 +882,15 @@
if (child instanceof TaskView && child != mSplitHiddenTaskView
&& child != mMovingTaskView) {
TaskView taskView = (TaskView) child;
- mHasVisibleTaskData.delete(taskView.getTaskIds()[0]);
+ for (int i : taskView.getTaskIds()) {
+ mHasVisibleTaskData.delete(i);
+ }
+ if (child instanceof GroupedTaskView) {
+ mGroupedTaskViewPool.recycle((GroupedTaskView)taskView);
+ } else {
+ mTaskViewPool.recycle(taskView);
+ }
taskView.setTaskViewId(-1);
- mTaskViewPool.recycle(taskView);
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
updateTaskStartIndex(child);
@@ -880,6 +921,21 @@
mSideTaskLaunchCallback.add(callback::executeAllAndDestroy);
}
+ /**
+ * This is a one-time callback when touching in live tile mode. It's reset to null right
+ * after it's called.
+ */
+ public void setTaskLaunchListener(TaskLaunchListener taskLaunchListener) {
+ mTaskLaunchListener = taskLaunchListener;
+ }
+
+ public void onTaskLaunchedInLiveTileMode() {
+ if (mTaskLaunchListener != null) {
+ mTaskLaunchListener.onTaskLaunched();
+ mTaskLaunchListener = null;
+ }
+ }
+
private void executeSideTaskLaunchCallback() {
if (mSideTaskLaunchCallback != null) {
mSideTaskLaunchCallback.executeAllAndDestroy();
@@ -887,10 +943,15 @@
}
}
+ /**
+ * TODO(b/195675206) Check both taskIDs from runningTaskViewId
+ * and launch if either of them is {@param taskId}
+ */
public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
int runningTaskViewId = getTaskViewIdFromTaskId(taskId);
if (mRunningTaskViewId != -1 && mRunningTaskViewId == runningTaskViewId) {
- RemoteAnimationTargets targets = getLiveTileParams().getTargetSet();
+ TransformParams params = mRemoteTargetHandles[0].getTransformParams();
+ RemoteAnimationTargets targets = params.getTargetSet();
if (targets != null && targets.findTask(taskId) != null) {
launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
targets.nonApps);
@@ -958,6 +1019,43 @@
}
}
+ private boolean isLastGridTaskVisible() {
+ TaskView lastTaskView = getLastGridTaskView();
+ return lastTaskView != null && lastTaskView.isVisibleToUser();
+ }
+
+ private TaskView getLastGridTaskView() {
+ IntArray topRowIdArray = getTopRowIdArray();
+ IntArray bottomRowIdArray = getBottomRowIdArray();
+ if (topRowIdArray.isEmpty() && bottomRowIdArray.isEmpty()) {
+ return null;
+ }
+ int lastTaskViewId = topRowIdArray.size() >= bottomRowIdArray.size() ? topRowIdArray.get(
+ topRowIdArray.size() - 1) : bottomRowIdArray.get(bottomRowIdArray.size() - 1);
+ return getTaskViewFromTaskViewId(lastTaskViewId);
+ }
+
+ private int getSnapToLastTaskScrollDiff() {
+ // Snap to a position where ClearAll is just invisible.
+ int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+ int clearAllScroll = getScrollForPage(indexOfChild(mClearAllButton));
+ int targetScroll = clearAllScroll + (mIsRtl ? clearAllWidth : -clearAllWidth);
+ return screenStart - targetScroll;
+ }
+
+ private int getSnapToFocusedTaskScrollDiff(boolean isClearAllHidden) {
+ int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int targetScroll = getScrollForPage(indexOfChild(getFocusedTaskView()));
+ if (!isClearAllHidden) {
+ int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+ int taskGridHorizontalDiff = mLastComputedTaskSize.right - mLastComputedGridSize.right;
+ int clearAllFocusScrollDiff = taskGridHorizontalDiff - clearAllWidth;
+ targetScroll += mIsRtl ? clearAllFocusScrollDiff : -clearAllFocusScrollDiff;
+ }
+ return screenStart - targetScroll;
+ }
+
private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
showAsFullscreen(), showAsGrid());
@@ -996,10 +1094,21 @@
if (!enabled) {
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
- mTmpRunningTask = null;
+ mTmpRunningTasks = null;
if (mSplitSelectStateController.isSplitSelectActive()) {
cancelSplitSelect(false);
}
+ // Remove grouped tasks and recycle once we exit overview
+ int taskCount = getTaskViewCount();
+ for (int i = 0; i < taskCount; i++) {
+ View v = getTaskViewAt(i);
+ if (!(v instanceof GroupedTaskView)) {
+ return;
+ }
+ GroupedTaskView gtv = (GroupedTaskView) v;
+ gtv.onTaskListVisibilityChanged(false);
+ removeView(gtv);
+ }
}
updateLocusId();
}
@@ -1026,7 +1135,6 @@
super.onPageEndTransition();
if (isClearAllHidden()) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
- } else {
}
if (getNextPage() > 0) {
setSwipeDownShouldLaunchApp(true);
@@ -1134,6 +1242,25 @@
}
@Override
+ protected void onEdgeAbsorbingScroll() {
+ vibrateForScroll();
+ }
+
+ @Override
+ protected void onScrollOverPageChanged() {
+ vibrateForScroll();
+ }
+
+ private void vibrateForScroll() {
+ long now = SystemClock.uptimeMillis();
+ if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) {
+ mScrollLastHapticTimestamp = now;
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE,
+ SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK);
+ }
+ }
+
+ @Override
protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
// Enables swiping to the left or right only if the task overlay is not modal.
if (!isModal()) {
@@ -1170,7 +1297,7 @@
mMovingTaskView = focusedTaskView;
removeView(focusedTaskView);
mMovingTaskView = null;
- focusedTaskView.onRecycle();
+ focusedTaskView.resetPersistentViewTransforms();
addView(focusedTaskView, mTaskViewStartIndex);
setCurrentPage(mTaskViewStartIndex);
@@ -1201,18 +1328,33 @@
TaskView ignoreResetTaskView =
mIgnoreResetTaskId == -1 ? null : getTaskViewByTaskId(mIgnoreResetTaskId);
- final int requiredTaskCount = tasks.size();
- if (getTaskViewCount() != requiredTaskCount) {
+ int[] splitTaskIds =
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getPersistentSplitIds();
+ int requiredGroupTaskViews = splitTaskIds.length / 2;
+
+ // Subtract half the number of split tasks and not total number because we've already
+ // added a GroupedTaskView when swipe up gesture happens.
+ // This will need to change if we start showing GroupedTaskViews during swipe up from home
+ int requiredTaskViewCount = tasks.size() - requiredGroupTaskViews;
+
+ if (getTaskViewCount() != requiredTaskViewCount) {
if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
}
- for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
- addView(getTaskViewFromPool());
+
+ for (int i = getTaskViewCount(); i < requiredTaskViewCount; i++) {
+ addView(getTaskViewFromPool(false));
}
- while (getTaskViewCount() > requiredTaskCount) {
+ while (getTaskViewCount() > requiredTaskViewCount) {
removeView(getChildAt(getChildCount() - 1));
}
- if (requiredTaskCount > 0) {
+ int groupedTaskViewCount = getGroupedTaskViewCount();
+ while (requiredGroupTaskViews > groupedTaskViewCount) {
+ // Add to front of list
+ addView(getTaskViewFromPool(true), 0);
+ requiredGroupTaskViews--;
+ }
+ if (requiredTaskViewCount > 0) {
addView(mClearAllButton);
}
}
@@ -1222,13 +1364,33 @@
// TODO set these type to array and check all taskIDs? Maybe we can get away w/ only one
int runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId)[0];
int focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId)[0];
+ Log.d(TASK_VIEW_ID_CRASH, "runningTaskId beforeBind: " + runningTaskId
+ + " runningTaskViewId: " + mRunningTaskViewId
+ + " forTaskView: " + getTaskViewFromTaskViewId(mRunningTaskViewId));
- // Rebind and reset all task views
- for (int i = requiredTaskCount - 1; i >= 0; i--) {
- final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
- final Task task = tasks.get(i);
+ for (int taskViewIndex = requiredTaskViewCount - 1, taskDataIndex = tasks.size() - 1;
+ taskViewIndex >= 0;
+ taskViewIndex--, taskDataIndex--) {
+ final int pageIndex = requiredTaskViewCount - taskViewIndex - 1 + mTaskViewStartIndex;
+ final Task task = tasks.get(taskDataIndex);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
- taskView.bind(task, mOrientationState);
+ if (taskView instanceof GroupedTaskView) {
+ Task leftTop;
+ Task rightBottom;
+ if (task.key.id == splitTaskIds[0]) {
+ leftTop = task;
+ taskDataIndex--;
+ rightBottom = tasks.get(taskDataIndex);
+ } else {
+ rightBottom = task;
+ taskDataIndex--;
+ leftTop = tasks.get(taskDataIndex);
+ }
+ ((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState,
+ mSplitBoundsConfig);
+ } else {
+ taskView.bind(task, mOrientationState);
+ }
}
// Keep same previous focused task
@@ -1240,28 +1402,48 @@
mFocusedTaskViewId = newFocusedTaskView != null ?
newFocusedTaskView.getTaskViewId() : -1;
updateTaskSize();
+ updateChildTaskOrientations();
TaskView newRunningTaskView = null;
if (runningTaskId != -1) {
// Update mRunningTaskViewId to be the new TaskView that was assigned by binding
// the full list of tasks to taskViews
newRunningTaskView = getTaskViewByTaskId(runningTaskId);
- mRunningTaskViewId = newRunningTaskView.getTaskViewId();
+ if (newRunningTaskView == null) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = requiredTaskViewCount - 1; i >= 0; i--) {
+ final int pageIndex = requiredTaskViewCount - i - 1 + mTaskViewStartIndex;
+ final TaskView taskView = (TaskView) getChildAt(pageIndex);
+ int taskViewId = taskView.getTaskViewId();
+ sb.append(" taskViewId: " + taskViewId
+ + " taskId: " + getTaskIdsForTaskViewId(taskViewId)[0]
+ + " for taskView: " + taskView + "\n");
+ }
+ Log.d(TASK_VIEW_ID_CRASH, "taskViewCount: " + getTaskViewCount()
+ + " " + sb.toString());
+ mRunningTaskViewId = -1;
+ } else {
+ mRunningTaskViewId = newRunningTaskView.getTaskViewId();
+ }
}
+ int targetPage = -1;
if (mNextPage == INVALID_PAGE) {
// Set the current page to the running task, but not if settling on new task.
if (runningTaskId != -1) {
- setCurrentPage(indexOfChild(newRunningTaskView));
+ targetPage = indexOfChild(newRunningTaskView);
} else if (getTaskViewCount() > 0) {
- setCurrentPage(indexOfChild(getTaskViewAt(0)));
+ targetPage = indexOfChild(getTaskViewAt(0));
}
} else if (currentTaskId != -1) {
currentTaskView = getTaskViewByTaskId(currentTaskId);
if (currentTaskView != null) {
- setCurrentPage(indexOfChild(currentTaskView));
+ targetPage = indexOfChild(currentTaskView);
}
}
+ if (targetPage != -1 && mCurrentPage != targetPage) {
+ setCurrentPage(targetPage);
+ }
if (mIgnoreResetTaskId != -1 &&
getTaskViewByTaskId(mIgnoreResetTaskId) != ignoreResetTaskView) {
@@ -1300,6 +1482,16 @@
return taskViewCount;
}
+ public int getGroupedTaskViewCount() {
+ int groupViewCount = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i) instanceof GroupedTaskView) {
+ groupViewCount++;
+ }
+ }
+ return groupViewCount;
+ }
+
protected void onTaskStackUpdated() {
// Lazily update the empty message only when the task stack is reapplied
updateEmptyMessage();
@@ -1320,12 +1512,13 @@
// Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
// to reset the params after it settles in Overview from swipe up so that we don't
// render with obsolete param values.
- mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 0;
- mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
- mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
- mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-
- mLiveTileParams.setTargetAlpha(1);
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
+ simulator.taskPrimaryTranslation.value = 0;
+ simulator.taskSecondaryTranslation.value = 0;
+ simulator.fullScreenProgress.value = 0;
+ simulator.recentsViewScale.value = 1;
+ });
// Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
// null.
@@ -1379,11 +1572,12 @@
setPageSpacing(dp.overviewPageSpacing);
// Propagate DeviceProfile change event.
- mLiveTileTaskViewSimulator.setDp(dp);
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDp(dp));
mActionsView.setDp(dp);
mOrientationState.setDeviceProfile(dp);
- // Update RecentsView adn TaskView's DeviceProfile dependent layout.
+ // Update RecentsView and TaskView's DeviceProfile dependent layout.
updateOrientationHandler();
}
@@ -1490,8 +1684,7 @@
}
public void getTaskSize(Rect outRect) {
- mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
- mOrientationHandler);
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
mLastComputedTaskSize.set(outRect);
}
@@ -1499,8 +1692,7 @@
* Returns the size of task selected to enter modal state.
*/
public Point getSelectedTaskSize() {
- mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect,
- mOrientationHandler);
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect);
return new Point(mTempRect.width(), mTempRect.height());
}
@@ -1531,28 +1723,28 @@
// After scrolling, update the visible task's data
loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
-
- // After scrolling, update ActionsView's visibility.
- updateActionsViewScrollAlpha();
}
+ // Update ActionsView's visibility when scroll changes.
+ updateActionsViewFocusedScroll();
+
// Update the high res thumbnail loader state
mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
return scrolling;
}
- private void updateActionsViewScrollAlpha() {
- float scrollAlpha = 1f;
+ private void updateActionsViewFocusedScroll() {
+ boolean hiddenFocusedScroll;
if (showAsGrid()) {
TaskView focusedTaskView = getFocusedTaskView();
- if (focusedTaskView != null) {
- float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
- - mOrientationHandler.getPrimaryScroll(this));
- float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
- scrollAlpha = Utilities.boundToRange(delta, 0, 1);
- }
+ hiddenFocusedScroll = focusedTaskView == null
+ || getScrollForPage(indexOfChild(focusedTaskView))
+ != mOrientationHandler.getPrimaryScroll(this);
+ } else {
+ hiddenFocusedScroll = false;
}
- mActionsView.getScrollAlpha().setValue(scrollAlpha);
+ mActionsView.updateHiddenFlags(OverviewActionsView.HIDDEN_FOCUSED_SCROLL,
+ hiddenFocusedScroll);
}
/**
@@ -1633,8 +1825,17 @@
visible = lower <= index && index <= upper;
}
if (visible) {
- if (task == mTmpRunningTask) {
- // Skip loading if this is the task that we are animating into
+ boolean skipLoadingTask = false;
+ if (mTmpRunningTasks != null) {
+ for (Task t : mTmpRunningTasks) {
+ if (task == t) {
+ // Skip loading if this is the task that we are animating into
+ skipLoadingTask = true;
+ break;
+ }
+ }
+ }
+ if (skipLoadingTask) {
continue;
}
if (!mHasVisibleTaskData.get(task.key.id)) {
@@ -1698,7 +1899,7 @@
setCurrentTask(-1);
mIgnoreResetTaskId = -1;
mTaskListChangeId = -1;
- mFocusedTaskViewId = getTaskViewCount() > 0 ? getTaskViewAt(0).getTaskViewId() : -1;
+ mFocusedTaskViewId = -1;
if (mRecentsAnimationController != null) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
@@ -1709,7 +1910,8 @@
}
}
setEnableDrawingLiveTile(false);
- mLiveTileParams.setTargetSet(null);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
+ .setTargetSet(null));
// These are relatively expensive and don't need to be done this frame (RecentsView isn't
// visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -1776,8 +1978,10 @@
* Handle the edge case where Recents could increment task count very high over long
* period of device usage. Probably will never happen, but meh.
*/
- private TaskView getTaskViewFromPool() {
- TaskView taskView = mTaskViewPool.getView();
+ private <T extends TaskView> T getTaskViewFromPool(boolean isGrouped) {
+ T taskView = isGrouped ?
+ (T) mGroupedTaskViewPool.getView() :
+ (T) mTaskViewPool.getView();
taskView.setTaskViewId(mTaskViewIdCount);
if (mTaskViewIdCount == Integer.MAX_VALUE) {
mTaskViewIdCount = 0;
@@ -1813,7 +2017,7 @@
/**
* Called when a gesture from an app is starting.
*/
- public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
+ public void onGestureAnimationStart(RunningTaskInfo[] runningTaskInfo) {
mGestureActive = true;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
@@ -1864,7 +2068,6 @@
return as;
}
-
private void updateChildTaskOrientations() {
for (int i = 0; i < getTaskViewCount(); i++) {
getTaskViewAt(i).setOrientationState(mOrientationState);
@@ -1880,10 +2083,9 @@
*/
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
- TaskViewSimulator taskViewSimulator) {
+ TaskViewSimulator[] taskViewSimulators) {
mCurrentGestureEndTarget = endTarget;
if (endTarget == GestureState.GestureEndTarget.RECENTS) {
- setEnableFreeScroll(true);
updateGridProperties();
}
@@ -1897,13 +2099,16 @@
runningTaskView.getGridTranslationX(),
runningTaskView.getGridTranslationY());
}
- if (animatorSet == null) {
- setGridProgress(1);
- taskViewSimulator.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation;
- } else {
- animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
- animatorSet.play(taskViewSimulator.taskPrimaryTranslation.animateToValue(
- runningTaskPrimaryGridTranslation));
+ for (TaskViewSimulator tvs : taskViewSimulators) {
+ if (animatorSet == null) {
+ setGridProgress(1);
+ tvs.taskPrimaryTranslation.value =
+ runningTaskPrimaryGridTranslation;
+ } else {
+ animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
+ animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
+ runningTaskPrimaryGridTranslation));
+ }
}
}
}
@@ -1932,7 +2137,21 @@
/**
* Returns true if we should add a stub taskView for the running task id
*/
- protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
+ protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
+ if (runningTaskInfos.length > 1) {
+ // * Always create new view for GroupedTaskView
+ // * Remove existing associated taskViews for tasks currently in split
+ for (RunningTaskInfo rti : runningTaskInfos) {
+ TaskView taskView = getTaskViewByTaskId(rti.taskId);
+ if (taskView == null) {
+ continue;
+ }
+ taskView.onTaskListVisibilityChanged(false);
+ removeView(taskView);
+ }
+ return true;
+ }
+ RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.taskId) == null;
}
@@ -1942,29 +2161,47 @@
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
* is called. Also scrolls the view to this task.
*/
- public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
+ public void showCurrentTask(RunningTaskInfo[] runningTaskInfo) {
int runningTaskViewId = -1;
+ boolean needGroupTaskView = runningTaskInfo.length > 1;
+ RunningTaskInfo taskInfo = runningTaskInfo[0];
if (shouldAddStubTaskView(runningTaskInfo)) {
boolean wasEmpty = getChildCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
- final TaskView taskView = getTaskViewFromPool();
+ final TaskView taskView;
+ if (needGroupTaskView) {
+ taskView = getTaskViewFromPool(true);
+ RunningTaskInfo secondaryTaskInfo = runningTaskInfo[1];
+ mTmpRunningTasks = new Task[]{
+ Task.from(new TaskKey(taskInfo), taskInfo, false),
+ Task.from(new TaskKey(secondaryTaskInfo), secondaryTaskInfo, false)
+ };
+ addView(taskView, mTaskViewStartIndex);
+ // When we create a placeholder task view mSplitBoundsConfig will be null, but with
+ // the actual app running we won't need to show the thumbnail until all the tasks
+ // load later anyways
+ ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
+ mOrientationState, mSplitBoundsConfig);
+ } else {
+ taskView = getTaskViewFromPool(false);
+ addView(taskView, mTaskViewStartIndex);
+ // The temporary running task is only used for the duration between the start of the
+ // gesture and the task list is loaded and applied
+ mTmpRunningTasks = new Task[]{Task.from(new TaskKey(taskInfo), taskInfo, false)};
+ taskView.bind(mTmpRunningTasks[0], mOrientationState);
+ }
runningTaskViewId = taskView.getTaskViewId();
- addView(taskView, mTaskViewStartIndex);
if (wasEmpty) {
addView(mClearAllButton);
}
- // The temporary running task is only used for the duration between the start of the
- // gesture and the task list is loaded and applied
- mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
- taskView.bind(mTmpRunningTask, mOrientationState);
// Measure and layout immediately so that the scroll values is updated instantly
// as the user might be quick-switching
measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
makeMeasureSpec(getMeasuredHeight(), EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
- } else if (getTaskViewByTaskId(runningTaskInfo.taskId) != null) {
- runningTaskViewId = getTaskViewByTaskId(runningTaskInfo.taskId).getTaskViewId();
+ } else if (!needGroupTaskView && getTaskViewByTaskId(taskInfo.taskId) != null) {
+ runningTaskViewId = getTaskViewByTaskId(taskInfo.taskId).getTaskViewId();
}
boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -1975,6 +2212,7 @@
setRunningTaskHidden(runningTaskTileHidden);
// Update task size after setting current task.
updateTaskSize();
+ updateChildTaskOrientations();
// Reload the task list
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
@@ -1984,6 +2222,8 @@
* Sets the running task id, cleaning up the old running task if necessary.
*/
public void setCurrentTask(int runningTaskViewId) {
+ Log.d(TASK_VIEW_ID_CRASH, "currentRunningTaskViewId: " + mRunningTaskViewId
+ + " requestedTaskViewId: " + runningTaskViewId);
if (mRunningTaskViewId == runningTaskViewId) {
return;
}
@@ -2054,23 +2294,39 @@
}
}
- /** Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
+ /**
+ * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
* layout.
* This method is used when no task dismissal has occurred.
*/
private void updateGridProperties() {
- updateGridProperties(false);
+ updateGridProperties(false, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
+ * layout.
+ *
+ * This method is used when task dismissal has occurred, but rebalance is not needed.
+ *
+ * @param isTaskDismissal indicates if update was called due to task dismissal
+ */
+ private void updateGridProperties(boolean isTaskDismissal) {
+ updateGridProperties(isTaskDismissal, Integer.MAX_VALUE);
}
/**
* Updates TaskView and ClearAllButton scaling and translation required to turn into grid
* layout.
+ *
* This method only calculates the potential position and depends on {@link #setGridProgress} to
* apply the actual scaling and translation.
*
- * @param isTaskDismissal indicates if update was called due to task dismissal
+ * @param isTaskDismissal indicates if update was called due to task dismissal
+ * @param startRebalanceAfter which view index to start rebalancing from. Use Integer.MAX_VALUE
+ * to skip rebalance
*/
- private void updateGridProperties(boolean isTaskDismissal) {
+ private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAfter) {
int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
@@ -2137,8 +2393,20 @@
focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
}
int taskViewId = taskView.getTaskViewId();
- boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskViewId)
- : topRowWidth <= bottomRowWidth;
+
+ // Rebalance the grid starting after a certain index
+ boolean isTopRow;
+ if (isTaskDismissal) {
+ if (i > startRebalanceAfter) {
+ mTopRowIdSet.remove(taskViewId);
+ isTopRow = topRowWidth <= bottomRowWidth;
+ } else {
+ isTopRow = mTopRowIdSet.contains(taskViewId);
+ }
+ } else {
+ isTopRow = topRowWidth <= bottomRowWidth;
+ }
+
if (isTopRow) {
if (homeTaskView != null && nextFocusedTaskView == null) {
// TaskView will be focused when swipe up, don't count towards row width.
@@ -2351,10 +2619,16 @@
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// alpha is set to 0 so that it can be recycled in the view pool properly
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
- anim.setFloat(mLiveTileParams, TransformParams.TARGET_ALPHA, 0,
- clampToProgress(ACCEL, 0, 0.5f));
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ TransformParams params = remoteTargetHandle.getTransformParams();
+ anim.setFloat(params, TransformParams.TARGET_ALPHA, 0,
+ clampToProgress(FINAL_FRAME, 0, 0.5f));
+ });
}
- anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
+ boolean isTaskInBottomGridRow = showAsGrid() && !mTopRowIdSet.contains(
+ taskView.getTaskViewId()) && taskView.getTaskViewId() != mFocusedTaskViewId;
+ anim.setFloat(taskView, VIEW_ALPHA, 0,
+ clampToProgress(isTaskInBottomGridRow ? ACCEL : FINAL_FRAME, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
taskView.getSecondaryDissmissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -2371,10 +2645,12 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
&& taskView.isRunningTask()) {
anim.addOnFrameCallback(() -> {
- mLiveTileTaskViewSimulator.taskSecondaryTranslation.value =
- mOrientationHandler.getSecondaryValue(
- taskView.getTranslationX(),
- taskView.getTranslationY());
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .taskSecondaryTranslation.value = mOrientationHandler
+ .getSecondaryValue(taskView.getTranslationX(),
+ taskView.getTranslationY()
+ ));
redrawLiveTile();
});
}
@@ -2387,8 +2663,8 @@
private void createInitialSplitSelectAnimation(PendingAnimation anim) {
float placeholderHeight = getResources().getDimension(R.dimen.split_placeholder_size);
mOrientationHandler.getInitialSplitPlaceholderBounds((int) placeholderHeight,
- mActivity.getDeviceProfile(),
- mSplitSelectStateController.getActiveSplitPositionOption(), mTempRect);
+ mActivity.getDeviceProfile(),
+ mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
RectF startingTaskRect = new RectF();
mSplitHiddenTaskView.setVisibility(INVISIBLE);
@@ -2425,7 +2701,6 @@
boolean showAsGrid = showAsGrid();
int taskCount = getTaskViewCount();
int dismissedIndex = indexOfChild(dismissedTaskView);
- int dismissedTaskId = dismissedTaskView.getTaskIds()[0];
int dismissedTaskViewId = dismissedTaskView.getTaskViewId();
// Grid specific properties.
@@ -2474,6 +2749,94 @@
}
}
+ float dismissTranslationInterpolationEnd = 1;
+ boolean closeGapBetweenClearAll = false;
+ boolean isClearAllHidden = isClearAllHidden();
+ if (showAsGrid && isLastGridTaskVisible()) {
+ // After dismissal, animate translation of the remaining tasks to fill any gap left
+ // between the end of the grid and the clear all button. Only animate if the clear
+ // all button is visible or would become visible after dismissal.
+ float longGridRowWidthDiff = 0;
+
+ int topGridRowSize = mTopRowIdSet.size();
+ int bottomGridRowSize = taskCount - mTopRowIdSet.size() - 1;
+ boolean topRowLonger = topGridRowSize > bottomGridRowSize;
+ boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
+ boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId);
+ boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed;
+ float gapWidth = 0;
+ if ((topRowLonger && dismissedTaskFromTop)
+ || (bottomRowLonger && dismissedTaskFromBottom)) {
+ gapWidth = dismissedTaskWidth;
+ } else if ((topRowLonger && nextFocusedTaskFromTop)
+ || (bottomRowLonger && !nextFocusedTaskFromTop)) {
+ gapWidth = nextFocusedTaskWidth;
+ }
+ if (gapWidth > 0) {
+ if (taskCount > 2) {
+ // Compensate the removed gap.
+ longGridRowWidthDiff += mIsRtl ? -gapWidth : gapWidth;
+ if (isClearAllHidden) {
+ // If ClearAllButton isn't fully shown, snap to the last task.
+ longGridRowWidthDiff += getSnapToLastTaskScrollDiff();
+ }
+ } else {
+ // If only focused task will be left, snap to focused task instead.
+ longGridRowWidthDiff += getSnapToFocusedTaskScrollDiff(isClearAllHidden);
+ }
+ }
+
+ // If we need to animate the grid to compensate the clear all gap, we split the second
+ // half of the dismiss pending animation (in which the non-dismissed tasks slide into
+ // place) in half again, making the first quarter the existing non-dismissal sliding
+ // and the second quarter this new animation of gap filling. This is due to the fact
+ // that PendingAnimation is a single animation, not a sequence of animations, so we
+ // fake it using interpolation.
+ if (longGridRowWidthDiff != 0) {
+ closeGapBetweenClearAll = true;
+ // Stagger the offsets of each additional task for a delayed animation. We use
+ // half here as this animation is half of half of an animation (1/4th).
+ float halfAdditionalDismissTranslationOffset =
+ (0.5f * ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET);
+ dismissTranslationInterpolationEnd = Utilities.boundToRange(
+ END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ + (taskCount - 1) * halfAdditionalDismissTranslationOffset,
+ END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff,
+ clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1));
+ dismissTranslationInterpolationEnd = Utilities.boundToRange(
+ dismissTranslationInterpolationEnd
+ - halfAdditionalDismissTranslationOffset,
+ END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+ && taskView.isRunningTask()) {
+ anim.addOnFrameCallback(() -> {
+ runActionOnRemoteHandles(
+ remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator()
+ .taskPrimaryTranslation.value =
+ TaskView.GRID_END_TRANSLATION_X.get(taskView));
+ redrawLiveTile();
+ });
+ }
+ }
+
+ // Change alpha of clear all if translating grid to hide it
+ if (isClearAllHidden) {
+ anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mClearAllButton.setDismissAlpha(1);
+ }
+ });
+ }
+ }
+ }
+
int distanceFromDismissedTask = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
@@ -2523,9 +2886,14 @@
&& child instanceof TaskView
&& ((TaskView) child).isRunningTask()) {
anim.addOnFrameCallback(() -> {
- mLiveTileTaskViewSimulator.taskPrimaryTranslation.value =
- mOrientationHandler.getPrimaryValue(child.getTranslationX(),
- child.getTranslationY());
+ runActionOnRemoteHandles(
+ remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator()
+ .taskPrimaryTranslation.value =
+ mOrientationHandler.getPrimaryValue(
+ child.getTranslationX(),
+ child.getTranslationY()
+ ));
redrawLiveTile();
});
}
@@ -2548,22 +2916,25 @@
float animationStartProgress = Utilities.boundToRange(
INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- * ++distanceFromDismissedTask, 0f, 1f);
+ * ++distanceFromDismissedTask, 0f,
+ dismissTranslationInterpolationEnd);
if (taskView == nextFocusedTaskView) {
// Enlarge the task to be focused next, and translate into focus position.
float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
float secondaryTranslation = -mTaskGridVerticalDiff;
if (!nextFocusedTaskFromTop) {
secondaryTranslation -= mTopBottomRowHeightDiff;
}
anim.setFloat(taskView, taskView.getSecondaryDissmissTranslationProperty(),
- secondaryTranslation,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
} else {
@@ -2571,7 +2942,8 @@
isFocusedTaskDismissed ? nextFocusedTaskWidth : dismissedTaskWidth;
anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
mIsRtl ? primaryTranslation : -primaryTranslation,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
}
}
}
@@ -2587,6 +2959,7 @@
mPendingAnimation = anim;
final TaskView finalNextFocusedTaskView = nextFocusedTaskView;
+ final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll;
mPendingAnimation.addEndListener(new Consumer<Boolean>() {
@Override
public void accept(Boolean success) {
@@ -2607,9 +2980,9 @@
if (ENABLE_QUICKSTEP_LIVE_TILE.get()
&& dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> removeTaskInternal(dismissedTaskId));
+ () -> removeTaskInternal(dismissedTaskViewId));
} else {
- removeTaskInternal(dismissedTaskId);
+ removeTaskInternal(dismissedTaskViewId);
}
mActivity.getStatsLogManager().logger()
.withItemInfo(dismissedTaskView.getItemInfo())
@@ -2622,14 +2995,72 @@
resetTaskVisuals();
int pageToSnapTo = mCurrentPage;
- if ((dismissedIndex < pageToSnapTo && !showAsGrid)
- || pageToSnapTo == taskCount - 1) {
- pageToSnapTo -= 1;
- }
+ mCurrentPageScrollDiff = 0;
+ int taskViewIdToSnapTo = -1;
if (showAsGrid) {
- int primaryScroll = mOrientationHandler.getPrimaryScroll(RecentsView.this);
- int currentPageScroll = getScrollForPage(pageToSnapTo);
- mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+ if (finalCloseGapBetweenClearAll) {
+ if (taskCount > 2) {
+ pageToSnapTo = indexOfChild(mClearAllButton);
+ if (isClearAllHidden) {
+ int clearAllWidth = mOrientationHandler.getPrimarySize(
+ mClearAllButton);
+ mCurrentPageScrollDiff =
+ isRtl() ? clearAllWidth : -clearAllWidth;
+ }
+ } else if (isClearAllHidden) {
+ // Snap to focused task if clear all is hidden.
+ pageToSnapTo = 0;
+ }
+ } else {
+ // Get the id of the task view we will snap to based on the current
+ // page's relative position as the order of indices change over time due
+ // to dismissals.
+ TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage);
+ if (snappedTaskView != null) {
+ if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) {
+ if (finalNextFocusedTaskView != null) {
+ taskViewIdToSnapTo =
+ finalNextFocusedTaskView.getTaskViewId();
+ } else {
+ taskViewIdToSnapTo = mFocusedTaskViewId;
+ }
+ } else {
+ int snappedTaskViewId = snappedTaskView.getTaskViewId();
+ boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
+ snappedTaskViewId);
+ IntArray taskViewIdArray =
+ isSnappedTaskInTopRow ? getTopRowIdArray()
+ : getBottomRowIdArray();
+ int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
+ taskViewIdArray.removeValue(dismissedTaskViewId);
+ if (finalNextFocusedTaskView != null) {
+ taskViewIdArray.removeValue(
+ finalNextFocusedTaskView.getTaskViewId());
+ }
+ if (snappedIndex < taskViewIdArray.size()) {
+ taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
+ } else if (snappedIndex == taskViewIdArray.size()) {
+ // If the snapped task is the last item from the
+ // dismissed row,
+ // snap to the same column in the other grid row
+ IntArray inverseRowTaskViewIdArray =
+ isSnappedTaskInTopRow ? getBottomRowIdArray()
+ : getTopRowIdArray();
+ if (snappedIndex < inverseRowTaskViewIdArray.size()) {
+ taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
+ snappedIndex);
+ }
+ }
+ }
+ }
+
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(
+ RecentsView.this);
+ int currentPageScroll = getScrollForPage(pageToSnapTo);
+ mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+ }
+ } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) {
+ pageToSnapTo--;
}
removeViewInLayout(dismissedTaskView);
mTopRowIdSet.remove(dismissedTaskViewId);
@@ -2644,13 +3075,66 @@
mTopRowIdSet.remove(mFocusedTaskViewId);
finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
}
- updateTaskSize(true);
+ updateTaskSize(/*isTaskDismissal=*/ true);
+ updateChildTaskOrientations();
// Update scroll and snap to page.
updateScrollSynchronously();
+
+ if (showAsGrid) {
+ // Rebalance tasks in the grid
+ int highestVisibleTaskIndex = getHighestVisibleTaskIndex();
+ if (highestVisibleTaskIndex < Integer.MAX_VALUE) {
+ TaskView taskView = getTaskViewAt(highestVisibleTaskIndex);
+
+ boolean shouldRebalance = false;
+ int screenStart = mOrientationHandler.getPrimaryScroll(
+ RecentsView.this);
+ int taskStart = mOrientationHandler.getChildStart(taskView)
+ + (int) taskView.getOffsetAdjustment(/*fullscreenEnabled=*/
+ false, /*gridEnabled=*/ true);
+
+ // Rebalance only if there is a maximum gap between the task and the
+ // screen's edge; this ensures that rebalanced tasks are outside the
+ // visible screen.
+ if (mIsRtl) {
+ shouldRebalance = taskStart <= screenStart + mPageSpacing;
+ } else {
+ int screenEnd =
+ screenStart + mOrientationHandler.getMeasuredSize(
+ RecentsView.this);
+ int taskSize = (int) (mOrientationHandler.getMeasuredSize(
+ taskView) * taskView
+ .getSizeAdjustment(/*fullscreenEnabled=*/false));
+ int taskEnd = taskStart + taskSize;
+
+ shouldRebalance = taskEnd >= screenEnd - mPageSpacing;
+ }
+
+ if (shouldRebalance) {
+ updateGridProperties(/*isTaskDismissal=*/ true,
+ highestVisibleTaskIndex);
+ updateScrollSynchronously();
+ }
+ }
+
+ // If snapping to another page due to indices rearranging, find the new
+ // index after dismissal & rearrange using the task view id.
+ if (taskViewIdToSnapTo != -1) {
+ pageToSnapTo = indexOfChild(
+ getTaskViewFromTaskViewId(taskViewIdToSnapTo));
+ }
+ }
setCurrentPage(pageToSnapTo);
+ // Update various scroll-dependent UI.
dispatchScrollChanged();
+ updateActionsViewFocusedScroll();
+ if (isClearAllHidden()) {
+ mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
+ false);
+ }
}
}
+ updateFocusedSplitButtonVisibility();
onDismissAnimationEnds();
mPendingAnimation = null;
}
@@ -2658,9 +3142,98 @@
return anim;
}
- private void removeTaskInternal(int dismissedTaskId) {
+ /**
+ * Shows split button if
+ * * We're in large screen
+ * * We're not already in split
+ * * There are at least 2 tasks to invoke split
+ */
+ private void updateFocusedSplitButtonVisibility() {
+ mActionsView.setSplitButtonVisible(mActivity.getDeviceProfile().isTablet &&
+ !(getRunningTaskView() instanceof GroupedTaskView) &&
+ getTaskViewCount() > 1
+ );
+ }
+
+ /**
+ * Returns all the tasks in the top row, without the focused task
+ */
+ private IntArray getTopRowIdArray() {
+ if (mTopRowIdSet.isEmpty()) {
+ return new IntArray(0);
+ }
+ IntArray topArray = new IntArray(mTopRowIdSet.size());
+ int taskViewCount = getTaskViewCount();
+ for (int i = 0; i < taskViewCount; i++) {
+ int taskViewId = getTaskViewAt(i).getTaskViewId();
+ if (mTopRowIdSet.contains(taskViewId)) {
+ topArray.add(taskViewId);
+ }
+ }
+ return topArray;
+ }
+
+ /**
+ * Returns all the tasks in the bottom row, without the focused task
+ */
+ private IntArray getBottomRowIdArray() {
+ int bottomRowIdArraySize = getTaskViewCount() - mTopRowIdSet.size() - 1;
+ if (bottomRowIdArraySize <= 0) {
+ return new IntArray(0);
+ }
+ IntArray bottomArray = new IntArray(bottomRowIdArraySize);
+ int taskViewCount = getTaskViewCount();
+ for (int i = 0; i < taskViewCount; i++) {
+ int taskViewId = getTaskViewAt(i).getTaskViewId();
+ if (!mTopRowIdSet.contains(taskViewId) && taskViewId != mFocusedTaskViewId) {
+ bottomArray.add(taskViewId);
+ }
+ }
+ return bottomArray;
+ }
+
+ /**
+ * Iterate the grid by columns instead of by TaskView index, starting after the focused task and
+ * up to the last balanced column.
+ *
+ * @return the highest visible TaskView index between both rows
+ */
+ private int getHighestVisibleTaskIndex() {
+ if (mTopRowIdSet.isEmpty()) return Integer.MAX_VALUE; // return earlier
+
+ int lastVisibleIndex = Integer.MAX_VALUE;
+ IntArray topRowIdArray = getTopRowIdArray();
+ IntArray bottomRowIdArray = getBottomRowIdArray();
+ int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size());
+
+ for (int i = 0; i < balancedColumns; i++) {
+ TaskView topTask = getTaskViewFromTaskViewId(topRowIdArray.get(i));
+
+ if (isTaskViewVisible(topTask)) {
+ TaskView bottomTask = getTaskViewFromTaskViewId(bottomRowIdArray.get(i));
+ lastVisibleIndex = Math.max(
+ indexOfChild(topTask) - mTaskViewStartIndex,
+ indexOfChild(bottomTask) - mTaskViewStartIndex
+ );
+ } else if (lastVisibleIndex < Integer.MAX_VALUE) {
+ break;
+ }
+ }
+
+ return lastVisibleIndex;
+ }
+
+ private void removeTaskInternal(int dismissedTaskViewId) {
+ int[] taskIds = getTaskIdsForTaskViewId(dismissedTaskViewId);
+ int primaryTaskId = taskIds[0];
+ int secondaryTaskId = taskIds[1];
UI_HELPER_EXECUTOR.getHandler().postDelayed(
- () -> ActivityManagerWrapper.getInstance().removeTask(dismissedTaskId),
+ () -> {
+ ActivityManagerWrapper.getInstance().removeTask(primaryTaskId);
+ if (secondaryTaskId != -1) {
+ ActivityManagerWrapper.getInstance().removeTask(secondaryTaskId);
+ }
+ },
REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
}
@@ -2668,8 +3241,7 @@
* @return {@code true} if one of the task thumbnails would intersect/overlap with the
* {@link #mFirstFloatingTaskView}
*/
- public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition
- int stagePosition) {
+ public boolean shouldShiftThumbnailsForSplitSelect(@StagePosition int stagePosition) {
if (!mActivity.getDeviceProfile().isTablet) {
// Never enough space on phones
return true;
@@ -2973,9 +3545,9 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // If we're going to HOME, avoid unnecessary onLayout that cause TaskViews to re-arrange
- // during animation to HOME.
- if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.HOME) {
+ // If we're going to a state without overview panel, avoid unnecessary onLayout that
+ // cause TaskViews to re-arrange during animation to that state.
+ if (!mOverviewStateEnabled && !mFirstLayout) {
return;
}
@@ -2993,7 +3565,9 @@
mLastComputedTaskStartPushOutDistance = null;
mLastComputedTaskEndPushOutDistance = null;
updatePageOffsets();
- mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .setScroll(getScrollOffset()));
setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
@@ -3057,7 +3631,9 @@
translationProperty.set(child, totalTranslation);
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
&& i == getRunningTaskIndex()) {
- mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = totalTranslation;
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .taskPrimaryTranslation.value = totalTranslation);
redrawLiveTile();
}
}
@@ -3106,8 +3682,11 @@
boolean isStartShift;
if (midpointIndex > -1) {
// When there is a midpoint reference task, adjacent tasks have less distance to travel
- // to reach offscreen. Offset the task position to the task's starting point.
- int midpointScroll = getScrollForPage(midpointIndex);
+ // to reach offscreen. Offset the task position to the task's starting point, and offset
+ // by current page's scroll diff.
+ int midpointScroll = getScrollForPage(midpointIndex)
+ + mOrientationHandler.getPrimaryScroll(this) - getScrollForPage(mCurrentPage);
+
getPersistentChildPosition(midpointIndex, midpointScroll, taskPosition);
float midpointStart = mOrientationHandler.getStart(taskPosition);
@@ -3160,7 +3739,9 @@
TaskView task = getTaskViewAt(i);
task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY());
}
- mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .recentsViewSecondaryTranslation.value = translation);
}
protected void setTaskViewsPrimarySplitTranslation(float translation) {
@@ -3192,12 +3773,18 @@
}
}
- public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
+ public void initiateSplitSelect(TaskView taskView) {
+ int defaultSplitPosition = mOrientationHandler
+ .getDefaultSplitPosition(mActivity.getDeviceProfile());
+ initiateSplitSelect(taskView, defaultSplitPosition);
+ }
+
+ public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition) {
mSplitHiddenTaskView = taskView;
Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
taskView.getBottom());
- mSplitSelectStateController.setInitialTaskSelect(taskView,
- splitPositionOption, initialBounds);
+ mSplitSelectStateController.setInitialTaskSelect(taskView.getTask(),
+ stagePosition, initialBounds);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
finishRecentsAnimation(true, null);
@@ -3223,7 +3810,7 @@
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
mActivity.getDeviceProfile(),
- mSplitSelectStateController.getActiveSplitPositionOption(), firstTaskEndingBounds,
+ mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
secondTaskEndingBounds);
mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
@@ -3238,7 +3825,7 @@
secondTaskEndingBounds, taskView.getThumbnail(),
true /*fadeWithThumbnail*/);
pendingAnimation.addEndListener(aBoolean -> {
- mSplitSelectStateController.setSecondTaskId(taskView);
+ mSplitSelectStateController.setSecondTaskId(taskView.getTask());
resetFromSplitSelectionState();
});
mSecondSplitHiddenTaskView = taskView;
@@ -3248,7 +3835,7 @@
public PendingAnimation cancelSplitSelect(boolean animate) {
SplitSelectStateController splitController = mSplitSelectStateController;
- SplitPositionOption splitOption = splitController.getActiveSplitPositionOption();
+ @StagePosition int stagePosition = splitController.getActiveSplitStagePosition();
Rect initialBounds = splitController.getInitialBounds();
splitController.resetState();
int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
@@ -3274,7 +3861,7 @@
if (child == mSplitHiddenTaskView) {
TaskView taskView = (TaskView) child;
- int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitOption,
+ int dir = mOrientationHandler.getSplitTaskViewDismissDirection(stagePosition,
mActivity.getDeviceProfile());
FloatProperty<TaskView> dismissingTaskViewTranslate;
Rect hiddenBounds = new Rect(taskView.getLeft(), taskView.getTop(),
@@ -3346,10 +3933,10 @@
return pendingAnim;
}
+ /** TODO(b/181707736) More gracefully handle exiting split selection state */
private void resetFromSplitSelectionState() {
mSplitHiddenTaskView.setTranslationY(0);
if (!showAsGrid()) {
- // TODO(b/186800707)
int pageToSnapTo = mCurrentPage;
if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
pageToSnapTo += 1;
@@ -3362,8 +3949,6 @@
resetTaskVisuals();
mSplitHiddenTaskView.setVisibility(VISIBLE);
mSplitHiddenTaskView = null;
- mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
- mSecondSplitHiddenTaskView = null;
mSplitHiddenTaskViewIndex = -1;
if (mFirstFloatingTaskView != null) {
mActivity.getRootView().removeView(mFirstFloatingTaskView);
@@ -3372,6 +3957,8 @@
if (mSecondFloatingTaskView != null) {
mActivity.getRootView().removeView(mSecondFloatingTaskView);
mSecondFloatingTaskView = null;
+ mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
+ mSecondSplitHiddenTaskView = null;
}
}
@@ -3467,10 +4054,12 @@
int runningTaskIndex = recentsView.getRunningTaskIndex();
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && runningTaskIndex != -1
&& runningTaskIndex != taskIndex) {
- anim.play(ObjectAnimator.ofFloat(
- recentsView.getLiveTileTaskViewSimulator().taskPrimaryTranslation,
- AnimatedFloat.VALUE,
- primaryTranslation));
+ for (RemoteTargetHandle remoteHandle : recentsView.getRemoteTargetHandles()) {
+ anim.play(ObjectAnimator.ofFloat(
+ remoteHandle.getTaskViewSimulator().taskPrimaryTranslation,
+ AnimatedFloat.VALUE,
+ primaryTranslation));
+ }
}
int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
@@ -3550,7 +4139,9 @@
mPendingAnimation = new PendingAnimation(duration);
mPendingAnimation.add(anim);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .addOverviewToAppAnim(mPendingAnimation, interpolator));
mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
}
mPendingAnimation.addEndListener(isSuccess -> {
@@ -3583,6 +4174,7 @@
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
+ updateFocusedSplitButtonVisibility();
loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
updateEnabledOverlays();
}
@@ -3641,30 +4233,49 @@
}
public void redrawLiveTile() {
- if (mLiveTileParams.getTargetSet() != null) {
- mLiveTileTaskViewSimulator.apply(mLiveTileParams);
- }
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ TransformParams params = remoteTargetHandle.getTransformParams();
+ if (params.getTargetSet() != null) {
+ remoteTargetHandle.getTaskViewSimulator().apply(params);
+ }
+ });
}
- public TaskViewSimulator getLiveTileTaskViewSimulator() {
- return mLiveTileTaskViewSimulator;
- }
-
- public TransformParams getLiveTileParams() {
- return mLiveTileParams;
+ public RemoteTargetHandle[] getRemoteTargetHandles() {
+ return mRemoteTargetHandles;
}
// TODO: To be removed in a follow up CL
public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
RecentsAnimationTargets recentsAnimationTargets) {
mRecentsAnimationController = recentsAnimationController;
- if (recentsAnimationTargets != null && recentsAnimationTargets.apps.length > 0) {
- if (mSyncTransactionApplier != null) {
- recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier);
- }
- mLiveTileTaskViewSimulator.setPreview(
- recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]);
- mLiveTileParams.setTargetSet(recentsAnimationTargets);
+ if (recentsAnimationTargets == null || recentsAnimationTargets.apps.length == 0) {
+ return;
+ }
+
+ if (mSyncTransactionApplier != null) {
+ recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier);
+ }
+
+ RemoteTargetGluer gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
+ mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
+ mSplitBoundsConfig = gluer.getStagedSplitBounds();
+ for (RemoteTargetHandle remoteTargetHandle : mRemoteTargetHandles) {
+ TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
+ tvs.setOrientationState(mOrientationState);
+ tvs.setDp(mActivity.getDeviceProfile());
+ tvs.recentsViewScale.value = 1;
+ }
+ }
+
+ /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
+ private void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
+ if (mRemoteTargetHandles == null) {
+ return;
+ }
+
+ for (RemoteTargetHandle handle : mRemoteTargetHandles) {
+ consumer.accept(handle);
}
}
@@ -3674,6 +4285,8 @@
public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
Runnable onFinishComplete) {
+ // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
+ mRemoteTargetHandles = null;
if (!toRecents && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// Reset the minimized state since we force-toggled the minimized state when entering
// overview, but never actually finished the recents animation. This is a catch all for
@@ -3945,7 +4558,7 @@
public void setOverviewGridEnabled(boolean overviewGridEnabled) {
if (mOverviewGridEnabled != overviewGridEnabled) {
mOverviewGridEnabled = overviewGridEnabled;
- updateActionsViewScrollAlpha();
+ updateActionsViewFocusedScroll();
// Request layout to ensure scroll position is recalculated with updated mGridProgress.
requestLayout();
}
@@ -3971,14 +4584,42 @@
}
return;
}
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
- switchToScreenshot(mRunningTaskViewId == -1 ? null
- : mRecentsAnimationController.screenshotTask(runningTaskId), onFinishRunnable);
+
+ switchToScreenshotInternal(onFinishRunnable);
+ }
+
+ private void switchToScreenshotInternal(Runnable onFinishRunnable) {
+ TaskView taskView = getRunningTaskView();
+ if (taskView == null) {
+ onFinishRunnable.run();
+ return;
+ }
+
+ taskView.setShowScreenshot(true);
+ for (TaskView.TaskIdAttributeContainer container :
+ taskView.getTaskIdAttributeContainers()) {
+ if (container == null) {
+ continue;
+ }
+
+ ThumbnailData td =
+ mRecentsAnimationController.screenshotTask(container.getTask().key.id);
+ TaskThumbnailView thumbnailView = container.getThumbnailView();
+ if (td != null) {
+ thumbnailView.setThumbnail(container.getTask(), td);
+ } else {
+ thumbnailView.refresh();
+ }
+ }
+ ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
}
/**
* Switch the current running task view to static snapshot mode, using the
* provided thumbnail data as the snapshot.
+ * TODO(b/195609063) Consolidate this method w/ the one above, except this thumbnail data comes
+ * from gesture state, which is a larger change of it having to keep track of multiple tasks.
+ * OR. Maybe it doesn't need to pass in a thumbnail and we can use the exact same flow as above
*/
public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
TaskView taskView = getRunningTaskView();
@@ -4131,7 +4772,8 @@
}
private void dispatchScrollChanged() {
- mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setScroll(getScrollOffset()));
for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
mScrollListeners.get(i).onScrollChanged();
}
@@ -4193,4 +4835,8 @@
// Set locus context is a binder call, don't want it to happen during a transition
UI_HELPER_EXECUTOR.post(() -> mActivity.setLocusContext(id, Bundle.EMPTY));
}
+
+ public interface TaskLaunchListener {
+ void onTaskLaunched();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index a712d1a..845e13e 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -24,8 +24,6 @@
import androidx.annotation.Nullable;
-import com.android.quickstep.util.SplitSelectStateController;
-
public class SplitPlaceholderView extends FrameLayout {
public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
@@ -42,34 +40,26 @@
}
};
- private SplitSelectStateController mSplitController;
- private IconView mIcon;
+ private IconView mIconView;
public SplitPlaceholderView(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public void init(SplitSelectStateController controller) {
- this.mSplitController = controller;
- }
-
- public SplitSelectStateController getSplitController() {
- return mSplitController;
- }
-
@Nullable
- public IconView getIcon() {
- return mIcon;
+ public IconView getIconView() {
+ return mIconView;
}
- public void setIcon(IconView icon) {
- if (mIcon == null) {
- mIcon = new IconView(getContext());
- addView(mIcon);
+ public void setIconView(IconView iconView, int iconSize) {
+ if (mIconView == null) {
+ mIconView = new IconView(getContext());
+ addView(mIconView);
}
- mIcon.setDrawable(icon.getDrawable());
- FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(icon.getLayoutParams());
+ mIconView.setDrawable(iconView.getDrawable());
+ mIconView.setDrawableSize(iconSize, iconSize);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(iconView.getLayoutParams());
params.gravity = Gravity.CENTER;
- mIcon.setLayoutParams(params);
+ mIconView.setLayoutParams(params);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 35e21ad..28044e4 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -211,10 +211,6 @@
return Insets.NONE;
}
- if (!TaskView.CLIP_STATUS_AND_NAV_BARS) {
- return Insets.NONE;
- }
-
RectF bitmapRect = new RectF(
0, 0,
mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
@@ -228,11 +224,14 @@
RectF boundsInBitmapSpace = new RectF();
boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
- return Insets.of(
- Math.round(boundsInBitmapSpace.left),
- Math.round(boundsInBitmapSpace.top),
- Math.round(bitmapRect.right - boundsInBitmapSpace.right),
- Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom));
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ int leftInset = TaskView.clipLeft(dp) ? Math.round(boundsInBitmapSpace.left) : 0;
+ int topInset = TaskView.clipTop(dp) ? Math.round(boundsInBitmapSpace.top) : 0;
+ int rightInset = TaskView.clipRight(dp) ? Math.round(
+ bitmapRect.right - boundsInBitmapSpace.right) : 0;
+ int bottomInset = TaskView.clipBottom(dp)
+ ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0;
+ return Insets.of(leftInset, topInset, rightInset, bottomInset);
}
@@ -440,8 +439,19 @@
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
- RectF thumbnailClipHint = TaskView.CLIP_STATUS_AND_NAV_BARS
- ? new RectF(thumbnailData.insets) : new RectF();
+ RectF thumbnailClipHint = new RectF();
+ if (TaskView.clipLeft(dp)) {
+ thumbnailClipHint.left = thumbnailData.insets.left;
+ }
+ if (TaskView.clipRight(dp)) {
+ thumbnailClipHint.right = thumbnailData.insets.right;
+ }
+ if (TaskView.clipTop(dp)) {
+ thumbnailClipHint.top = thumbnailData.insets.top;
+ }
+ if (TaskView.clipBottom(dp)) {
+ thumbnailClipHint.bottom = thumbnailData.insets.bottom;
+ }
float scale = thumbnailData.scale;
final float thumbnailScale;
@@ -554,7 +564,7 @@
-thumbnailClipHint.left * scale,
-thumbnailClipHint.top * scale);
} else {
- setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds);
+ setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds, dp);
}
final float widthWithInsets;
@@ -599,7 +609,7 @@
}
private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale,
- Rect thumbnailPosition) {
+ Rect thumbnailPosition, DeviceProfile dp) {
float newLeftInset = 0;
float newTopInset = 0;
float translateX = 0;
@@ -626,7 +636,7 @@
}
mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
mMatrix.postTranslate(translateX, translateY);
- if (TaskView.FULL_THUMBNAIL) {
+ if (TaskView.useFullThumbnail(dp)) {
mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
}
}
@@ -634,8 +644,8 @@
/**
* Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
*/
- public RectF getInsetsToDrawInFullscreen() {
- return TaskView.FULL_THUMBNAIL ? mClippedInsets : EMPTY_RECT_F;
+ public RectF getInsetsToDrawInFullscreen(DeviceProfile dp) {
+ return TaskView.useFullThumbnail(dp) ? mClippedInsets : EMPTY_RECT_F;
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 02c5d84..7c558c2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -91,6 +91,7 @@
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
@@ -98,18 +99,23 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.TaskCornerRadius;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.lang.annotation.Retention;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Stream;
/**
* A task in the Recents view.
@@ -141,15 +147,39 @@
public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
/**
- * Should the TaskView display clip off the status and navigation bars in recents. When this
- * is false the overview shows the whole screen scaled down instead.
+ * Should the TaskView display clip off the left inset in RecentsView.
*/
- public static final boolean CLIP_STATUS_AND_NAV_BARS = false;
+ public static boolean clipLeft(DeviceProfile deviceProfile) {
+ return false;
+ }
+
+ /**
+ * Should the TaskView display clip off the top inset in RecentsView.
+ */
+ public static boolean clipTop(DeviceProfile deviceProfile) {
+ return false;
+ }
+
+ /**
+ * Should the TaskView display clip off the right inset in RecentsView.
+ */
+ public static boolean clipRight(DeviceProfile deviceProfile) {
+ return false;
+ }
+
+ /**
+ * Should the TaskView display clip off the bottom inset in RecentsView.
+ */
+ public static boolean clipBottom(DeviceProfile deviceProfile) {
+ return deviceProfile.isTablet;
+ }
/**
* Should the TaskView scale down to fit whole thumbnail in fullscreen.
*/
- public static final boolean FULL_THUMBNAIL = false;
+ public static boolean useFullThumbnail(DeviceProfile deviceProfile) {
+ return deviceProfile.isTablet && !deviceProfile.isTaskbarPresentInApps;
+ }
private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
@@ -314,6 +344,19 @@
}
};
+ public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X =
+ new FloatProperty<TaskView>("gridEndTranslationX") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setGridEndTranslationX(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mGridEndTranslationX;
+ }
+ };
+
public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
new FloatProperty<TaskView>("snapshotScale") {
@Override
@@ -329,8 +372,8 @@
private final TaskOutlineProvider mOutlineProvider;
- private Task mTask;
- private TaskThumbnailView mSnapshotView;
+ protected Task mTask;
+ protected TaskThumbnailView mSnapshotView;
private IconView mIconView;
private final DigitalWellBeingToast mDigitalWellBeingToast;
private float mFullscreenProgress;
@@ -338,7 +381,7 @@
private float mNonGridScale = 1;
private float mDismissScale = 1;
private final FullscreenDrawParams mCurrentFullscreenParams;
- private final StatefulActivity mActivity;
+ protected final StatefulActivity mActivity;
// Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
private float mDismissTranslationX;
@@ -352,6 +395,8 @@
// The following grid translations scales with mGridProgress.
private float mGridTranslationX;
private float mGridTranslationY;
+ // The following grid translation is used to animate closing the gap between grid and clear all.
+ private float mGridEndTranslationX;
// Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
// switch.
private float mNonGridTranslationX;
@@ -367,7 +412,12 @@
private float mStableAlpha = 1;
private int mTaskViewId = -1;
- private final int[] mTaskIdContainer = new int[]{-1, -1};
+ /**
+ * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
+ */
+ protected final int[] mTaskIdContainer = new int[]{-1, -1};
+ protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer =
+ new TaskIdAttributeContainer[2];
private boolean mShowScreenshot;
@@ -518,10 +568,15 @@
cancelPendingLoadTasks();
mTask = task;
mTaskIdContainer[0] = mTask.key.id;
+ mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView);
mSnapshotView.bind(task);
setOrientationState(orientedState);
}
+ public TaskIdAttributeContainer[] getTaskIdAttributeContainers() {
+ return mTaskIdAttributeContainer;
+ }
+
public Task getTask() {
return mTask;
}
@@ -538,6 +593,11 @@
return mSnapshotView;
}
+ /** TODO(b/197033698) Remove all usages of above method and migrate to this one */
+ public TaskThumbnailView[] getThumbnails() {
+ return new TaskThumbnailView[]{mSnapshotView};
+ }
+
public IconView getIconView() {
return mIconView;
}
@@ -563,7 +623,28 @@
mIsClickableAsLiveTile = false;
RecentsView recentsView = getRecentsView();
- final RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
+ RemoteAnimationTargets targets;
+ RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
+ if (remoteTargetHandles.length == 1) {
+ targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
+ } else {
+ TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams();
+ TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams();
+ RemoteAnimationTargetCompat[] apps = Stream.concat(
+ Arrays.stream(topLeftParams.getTargetSet().apps),
+ Arrays.stream(rightBottomParams.getTargetSet().apps))
+ .toArray(RemoteAnimationTargetCompat[]::new);
+ RemoteAnimationTargetCompat[] wallpapers = Stream.concat(
+ Arrays.stream(topLeftParams.getTargetSet().wallpapers),
+ Arrays.stream(rightBottomParams.getTargetSet().wallpapers))
+ .toArray(RemoteAnimationTargetCompat[]::new);
+ RemoteAnimationTargetCompat[] nonApps = Stream.concat(
+ Arrays.stream(topLeftParams.getTargetSet().nonApps),
+ Arrays.stream(rightBottomParams.getTargetSet().nonApps))
+ .toArray(RemoteAnimationTargetCompat[]::new);
+ targets = new RemoteAnimationTargets(apps, wallpapers, nonApps,
+ topLeftParams.getTargetSet().targetMode);
+ }
if (targets == null) {
// If the recents animation is cancelled somehow between the parent if block and
// here, try to launch the task as a non live tile task.
@@ -584,6 +665,7 @@
}
});
anim.start();
+ recentsView.onTaskLaunchedInLiveTileMode();
} else {
launchTaskAnimated();
}
@@ -612,14 +694,21 @@
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null);
+ boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getPersistentSplitIds().length > 0;
if (ActivityManagerWrapper.getInstance()
.startActivityFromRecents(mTask.key, opts.options)) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
- getRecentsView().getRunningTaskViewId() != -1) {
+ if (isOldTaskSplit) {
+ SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(mTask.key.id);
+ }
+ RecentsView recentsView = getRecentsView();
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
+ recentsView.onTaskLaunchedInLiveTileMode();
+
// Return a fresh callback in the live tile case, so that it's not accidentally
// triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
RunnableList callbackList = new RunnableList();
- getRecentsView().addSideTaskLaunchCallback(callbackList);
+ recentsView.addSideTaskLaunchCallback(callbackList);
return callbackList;
}
return opts.onEndCallback;
@@ -720,11 +809,11 @@
}
}
- private boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
+ protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
return (dataChange & flag) == flag;
}
- private void cancelPendingLoadTasks() {
+ protected void cancelPendingLoadTasks() {
if (mThumbnailLoadRequest != null) {
mThumbnailLoadRequest.cancel();
mThumbnailLoadRequest = null;
@@ -759,6 +848,7 @@
RecentsView recentsView = getRecentsView();
recentsView.switchToScreenshot(
() -> recentsView.finishRecentsAnimation(true /* toRecents */,
+ false /* shouldPip */,
this::showTaskMenu));
} else {
showTaskMenu();
@@ -781,8 +871,11 @@
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int taskIconMargin = deviceProfile.overviewTaskMarginPx;
+ boolean isGridTask = deviceProfile.overviewShowAsGrid && !isFocusedTask();
int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
+ int taskMargin = isGridTask ? deviceProfile.overviewTaskMarginGridPx
+ : deviceProfile.overviewTaskMarginPx;
+ int taskIconMargin = snapshotParams.topMargin - taskIconHeight - taskMargin;
LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
switch (orientationHandler.getRotation()) {
case ROTATION_90:
@@ -814,6 +907,9 @@
iconParams.width = iconParams.height = taskIconHeight;
mIconView.setLayoutParams(iconParams);
mIconView.setRotation(orientationHandler.getDegreesRotated());
+ int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
+ : deviceProfile.overviewTaskIconDrawableSizePx;
+ mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
mSnapshotView.setLayoutParams(snapshotParams);
mSnapshotView.getTaskOverlay().updateOrientationState(orientationState);
@@ -871,11 +967,17 @@
setIconAndDimTransitionProgress(iconScale, invert);
}
+ protected void resetPersistentViewTransforms() {
+ mNonGridTranslationX = mNonGridTranslationY =
+ mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
+ resetViewTransforms();
+ }
+
protected void resetViewTransforms() {
// fullscreenTranslation and accumulatedTranslation should not be reset, as
// resetViewTransforms is called during Quickswitch scrolling.
- mDismissTranslationX = mTaskOffsetTranslationX = mTaskResistanceTranslationX =
- mSplitSelectTranslationX = 0f;
+ mDismissTranslationX = mTaskOffsetTranslationX =
+ mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f;
mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY =
mSplitSelectTranslationY = 0f;
setSnapshotScale(1f);
@@ -894,9 +996,7 @@
@Override
public void onRecycle() {
- mNonGridTranslationX = mNonGridTranslationY =
- mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
- resetViewTransforms();
+ resetPersistentViewTransforms();
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
mSnapshotView.setThumbnail(mTask, null);
@@ -977,7 +1077,6 @@
private void setNonGridScale(float nonGridScale) {
mNonGridScale = nonGridScale;
- updateCornerRadius();
applyScale();
}
@@ -1008,6 +1107,7 @@
scale *= mDismissScale;
setScaleX(scale);
setScaleY(scale);
+ updateSnapshotRadius();
}
/**
@@ -1088,6 +1188,11 @@
return mGridTranslationY;
}
+ private void setGridEndTranslationX(float gridEndTranslationX) {
+ mGridEndTranslationX = gridEndTranslationX;
+ applyTranslationX();
+ }
+
public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
if (gridEnabled) {
@@ -1117,7 +1222,7 @@
private void applyTranslationX() {
setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
- + mSplitSelectTranslationX + getPersistentTranslationX());
+ + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX());
}
private void applyTranslationY() {
@@ -1318,29 +1423,25 @@
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
- updateCornerRadius();
+ updateSnapshotRadius();
- mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
mOutlineProvider.updateParams(
mCurrentFullscreenParams,
mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
invalidateOutline();
}
- private void updateCornerRadius() {
+ private void updateSnapshotRadius() {
updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper());
+ mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
}
void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
if (getRecentsView() == null) {
return;
}
- mCurrentFullscreenParams.setProgress(
- mFullscreenProgress,
- getRecentsView().getScaleX(),
- mNonGridScale,
- getWidth(), mActivity.getDeviceProfile(),
- previewPositionHelper);
+ mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
+ getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper);
}
/**
@@ -1353,7 +1454,6 @@
float boxTranslationY;
int expectedWidth;
int expectedHeight;
- int iconDrawableSize;
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
if (deviceProfile.overviewShowAsGrid) {
final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx;
@@ -1369,13 +1469,11 @@
// that is associated with the original orientation of the focused task.
boxWidth = taskWidth;
boxHeight = taskHeight;
- iconDrawableSize = deviceProfile.overviewTaskIconDrawableSizePx;
} else {
// Otherwise task is in grid, and should use lastComputedGridTaskSize.
Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
boxWidth = lastComputedGridTaskSize.width();
boxHeight = lastComputedGridTaskSize.height();
- iconDrawableSize = deviceProfile.overviewTaskIconDrawableSizeGridPx;
}
// Bound width/height to the box size.
@@ -1392,7 +1490,6 @@
boxTranslationY = 0f;
expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT;
expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT;
- iconDrawableSize = deviceProfile.overviewTaskIconDrawableSizePx;
}
setNonGridScale(nonGridScale);
@@ -1402,7 +1499,6 @@
params.height = expectedHeight;
setLayoutParams(params);
}
- mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
}
private float getGridTrans(float endTranslation) {
@@ -1445,7 +1541,7 @@
public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU);
- getRecentsView().initiateSplitSelect(this, splitPositionOption);
+ getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
}
/**
@@ -1472,7 +1568,7 @@
public FullscreenDrawParams(Context context) {
mCornerRadius = TaskCornerRadius.get(context);
- mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
+ mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context);
mCurrentDrawnCornerRadius = mCornerRadius;
}
@@ -1482,12 +1578,16 @@
*/
public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
- RectF insets = pph.getInsetsToDrawInFullscreen();
+ RectF insets = pph.getInsetsToDrawInFullscreen(dp);
float currentInsetsLeft = insets.left * fullscreenProgress;
float currentInsetsRight = insets.right * fullscreenProgress;
+ float insetsBottom = insets.bottom;
+ if (dp.isTaskbarPresentInApps) {
+ insetsBottom = Math.max(0, insetsBottom - dp.taskbarSize);
+ }
mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress,
- currentInsetsRight, insets.bottom * fullscreenProgress);
+ currentInsetsRight, insetsBottom * fullscreenProgress);
float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius;
mCurrentDrawnCornerRadius =
@@ -1502,4 +1602,22 @@
}
}
+
+ public class TaskIdAttributeContainer {
+ private final TaskThumbnailView thumbnailView;
+ private final Task task;
+
+ public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView) {
+ this.task = task;
+ this.thumbnailView = thumbnailView;
+ }
+
+ public TaskThumbnailView getThumbnailView() {
+ return thumbnailView;
+ }
+
+ public Task getTask() {
+ return task;
+ }
+ }
}
diff --git a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
similarity index 65%
rename from quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
rename to quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index f82fbcc..c1b3beb 100644
--- a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -15,24 +15,31 @@
*/
package com.android.launcher3.model;
+import static android.os.Process.myUserHandle;
+
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.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.robolectric.Shadows.shadowOf;
+import static org.mockito.Mockito.doReturn;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetId;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
-import android.content.Context;
-import android.os.Process;
import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.config.FeatureFlags;
@@ -40,38 +47,34 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
-import com.android.launcher3.shadows.ShadowDeviceFlag;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowAppWidgetManager;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
+import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsPredicationUpdateTaskTest {
- private AppWidgetProviderInfo mApp1Provider1 = new AppWidgetProviderInfo();
- private AppWidgetProviderInfo mApp1Provider2 = new AppWidgetProviderInfo();
- private AppWidgetProviderInfo mApp2Provider1 = new AppWidgetProviderInfo();
- private AppWidgetProviderInfo mApp4Provider1 = new AppWidgetProviderInfo();
- private AppWidgetProviderInfo mApp4Provider2 = new AppWidgetProviderInfo();
- private AppWidgetProviderInfo mApp5Provider1 = new AppWidgetProviderInfo();
+ private AppWidgetProviderInfo mApp1Provider1;
+ private AppWidgetProviderInfo mApp1Provider2;
+ private AppWidgetProviderInfo mApp2Provider1;
+ private AppWidgetProviderInfo mApp4Provider1;
+ private AppWidgetProviderInfo mApp4Provider2;
+ private AppWidgetProviderInfo mApp5Provider1;
+ private List<AppWidgetProviderInfo> allWidgets;
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
- private Context mContext;
private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle;
@@ -80,54 +83,56 @@
@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());
- mContext = RuntimeEnvironment.application;
- mModelHelper = new LauncherModelHelper();
- mUserHandle = Process.myUserHandle();
+ mUserHandle = myUserHandle();
+ mApp1Provider1 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app1", "provider1"));
+ mApp1Provider2 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app1", "provider2"));
+ mApp2Provider1 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app2", "provider1"));
+ mApp4Provider1 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app4", "provider1"));
+ mApp4Provider2 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app4", ".provider2"));
+ mApp5Provider1 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app5", "provider1"));
+ allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
+ mApp4Provider1, mApp4Provider2, mApp5Provider1);
+
+ AppWidgetManager manager = mModelHelper.sandboxContext.spyService(AppWidgetManager.class);
+ doReturn(allWidgets).when(manager).getInstalledProviders();
+ doReturn(allWidgets).when(manager).getInstalledProvidersForProfile(eq(myUserHandle()));
+ doAnswer(i -> {
+ String pkg = i.getArgument(0);
+ Log.e("Hello", "Getting v " + pkg);
+ return TextUtils.isEmpty(pkg) ? allWidgets : allWidgets.stream()
+ .filter(a -> pkg.equals(a.provider.getPackageName()))
+ .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.txt");
+ mModelHelper.initializeData("widgets_predication_update_task_data");
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
- mApp1Provider1.provider = ComponentName.createRelative("app1", "provider1");
- ReflectionHelpers.setField(mApp1Provider1, "providerInfo",
- packageManager.addReceiverIfNotPresent(mApp1Provider1.provider));
- mApp1Provider2.provider = ComponentName.createRelative("app1", "provider2");
- ReflectionHelpers.setField(mApp1Provider2, "providerInfo",
- packageManager.addReceiverIfNotPresent(mApp1Provider2.provider));
- mApp2Provider1.provider = ComponentName.createRelative("app2", "provider1");
- ReflectionHelpers.setField(mApp2Provider1, "providerInfo",
- packageManager.addReceiverIfNotPresent(mApp2Provider1.provider));
- mApp4Provider1.provider = ComponentName.createRelative("app4", "provider1");
- ReflectionHelpers.setField(mApp4Provider1, "providerInfo",
- packageManager.addReceiverIfNotPresent(mApp4Provider1.provider));
- mApp4Provider2.provider = ComponentName.createRelative("app4", ".provider2");
- ReflectionHelpers.setField(mApp4Provider2, "providerInfo",
- packageManager.addReceiverIfNotPresent(mApp4Provider2.provider));
- mApp5Provider1.provider = ComponentName.createRelative("app5", "provider1");
- ReflectionHelpers.setField(mApp5Provider1, "providerInfo",
- packageManager.addReceiverIfNotPresent(mApp5Provider1.provider));
-
- ShadowAppWidgetManager shadowAppWidgetManager =
- shadowOf(mContext.getSystemService(AppWidgetManager.class));
- shadowAppWidgetManager.addInstalledProvider(mApp1Provider1);
- shadowAppWidgetManager.addInstalledProvider(mApp1Provider2);
- shadowAppWidgetManager.addInstalledProvider(mApp2Provider1);
- shadowAppWidgetManager.addInstalledProvider(mApp4Provider1);
- shadowAppWidgetManager.addInstalledProvider(mApp4Provider2);
- shadowAppWidgetManager.addInstalledProvider(mApp5Provider1);
-
- mModelHelper.getModel().addCallbacks(mCallback);
-
+ MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(mCallback)).get();
MODEL_EXECUTOR.post(() -> mModelHelper.getBgDataModel().widgetsModel.update(
- LauncherAppState.getInstance(mContext), /* packageUser= */ null));
- waitUntilIdle();
+ LauncherAppState.getInstance(mModelHelper.sandboxContext),
+ /* packageUser= */ null));
+
+ MODEL_EXECUTOR.submit(() -> { }).get();
+ MAIN_EXECUTOR.submit(() -> { }).get();
}
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
+ }
@Test
public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder()
@@ -165,9 +170,9 @@
@Test
public void widgetsRecommendationRan_localFilterDisabled_shouldReturnWidgetsInPredicationOrder()
throws Exception {
- ShadowDeviceFlag shadowDeviceFlag = Shadow.extract(
- FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER);
- shadowDeviceFlag.setValue(false);
+ if (FeatureFlags.ENABLE_LOCAL_RECOMMENDED_WIDGETS_FILTER.get()) {
+ return;
+ }
// WHEN newPredicationTask is executed with 5 predicated widgets.
AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
@@ -203,13 +208,8 @@
assertThat(actual.getUser()).isEqualTo(expected.getProfile());
}
- private void waitUntilIdle() {
- shadowOf(MODEL_EXECUTOR.getLooper()).idle();
- shadowOf(MAIN_EXECUTOR.getLooper()).idle();
- }
-
private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List<AppTarget> appTargets) {
- return new WidgetsPredictionUpdateTask(
+ return new WidgetsPredictionUpdateTask(
new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"),
appTargets);
}
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index e4f5a19..45e7e69 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -59,7 +59,6 @@
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureWatcher;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.quickstep.views.RecentsView;
import org.junit.After;
@@ -215,7 +214,6 @@
// b/143488140
//@NavigationModeSwitch
@Test
- @ScreenRecord // b/187080582
public void testOverview() {
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 67840d1..416c193 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -30,7 +30,6 @@
import android.content.pm.PackageManager;
import android.util.Log;
-import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -49,6 +48,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Test rule that allows executing a test with Quickstep on and then Quickstep off.
@@ -182,17 +182,12 @@
};
targetContext.getMainExecutor().execute(() ->
SYS_UI_NAVIGATION_MODE.addModeChangeListener(listener));
- // b/139137636
-// latch.await(60, TimeUnit.SECONDS);
+ latch.await(60, TimeUnit.SECONDS);
targetContext.getMainExecutor().execute(() ->
SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
- Wait.atMost(() -> "Navigation mode didn't change to " + expectedMode,
- () -> currentSysUiNavigationMode() == expectedMode, WAIT_TIME_MS,
- launcher);
- // b/139137636
-// assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
-// currentSysUiNavigationMode() == expectedMode, description);
+ assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
+ currentSysUiNavigationMode() == expectedMode, description);
}
@@ -221,12 +216,7 @@
private static void assertTrue(LauncherInstrumentation launcher, String message,
boolean condition, Description description) {
- if (launcher.getDevice().hasObject(By.textStartsWith(""))) {
- // The condition above is "screen is not empty". We are not treating
- // "Screen is empty" as an anomaly here. It's an acceptable state when
- // Launcher just starts under instrumentation.
- launcher.checkForAnomaly();
- }
+ launcher.checkForAnomaly(true);
if (!condition) {
final AssertionError assertionError = new AssertionError(message);
if (description != null) {
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
similarity index 98%
rename from quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
rename to quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 2b1b57c..159a51f 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -19,6 +19,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static org.junit.Assert.assertFalse;
@@ -40,6 +42,9 @@
import android.view.MotionEvent;
import android.view.Surface;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.util.DisplayController;
@@ -47,10 +52,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class OrientationTouchTransformerTest {
static class ScreenSize {
int mHeight;
@@ -293,7 +297,7 @@
}
private DisplayController.Info createDisplayInfo(ScreenSize screenSize, int rotation) {
- Context context = RuntimeEnvironment.application;
+ Context context = getApplicationContext();
Display display = spy(context.getSystemService(DisplayManager.class)
.getDisplay(DEFAULT_DISPLAY));
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 164e755..e5e560a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -37,7 +37,6 @@
import com.android.launcher3.tapl.OverviewActions;
import com.android.launcher3.tapl.OverviewTask;
import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.views.RecentsView;
@@ -158,7 +157,6 @@
@Test
@NavigationModeSwitch
@PortraitLandscape
- @ScreenRecord //b/193125090
public void testOverviewActions() throws Exception {
// Experimenting for b/165029151:
final Overview overview = mLauncher.pressHome().switchToOverview();
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index f33abb0..0f5a1c8 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -19,9 +19,9 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.InstrumentationRegistry.getTargetContext;
-import static com.android.launcher3.common.WidgetUtils.createWidgetInfo;
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;
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
import static org.junit.Assert.assertEquals;
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java b/quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
similarity index 89%
rename from quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
rename to quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
index 656379f..47ef13b 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
@@ -19,33 +19,34 @@
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_90;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.content.Context;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.quickstep.FallbackActivityInterface;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
/**
* Tests for {@link RecentsOrientedState}
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class RecentsOrientedStateTest {
private RecentsOrientedState mR1, mR2;
@Before
public void setup() {
- Context context = RuntimeEnvironment.application;
+ Context context = getApplicationContext();
mR1 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { });
mR2 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { });
assertEquals(mR1.getStateId(), mR2.getStateId());
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
new file mode 100644
index 0000000..9c5cfcd
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2020 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.Display.DEFAULT_DISPLAY;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.ReflectionHelpers;
+import com.android.quickstep.FallbackActivityInterface;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaskViewSimulatorTest {
+
+ @Test
+ public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(0, 80, 0, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(55, 80, 55, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(0, 80, 0, 40))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(0, 80, 120, 0))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(2450, 1250)
+ .withInsets(new Rect(55, 80, 55, 120))
+ .verifyNoTransforms();
+ }
+
+ @Test
+ public void taskProperlyScaled_landscape_rotated() {
+ new TaskMatrixVerifier()
+ .withLauncherSize(1200, 2450)
+ .withInsets(new Rect(0, 80, 0, 120))
+ .withAppBounds(
+ new Rect(0, 0, 2450, 1200),
+ new Rect(0, 80, 0, 120),
+ Surface.ROTATION_90)
+ .verifyNoTransforms();
+ }
+
+ private static class TaskMatrixVerifier extends TransformParams {
+
+ private Point mDisplaySize = new Point();
+ private Rect mDisplayInsets = new Rect();
+ private Rect mAppBounds = new Rect();
+ private Rect mLauncherInsets = new Rect();
+
+ private Rect mAppInsets;
+
+ private int mAppRotation = -1;
+ private DeviceProfile mDeviceProfile;
+
+ TaskMatrixVerifier withLauncherSize(int width, int height) {
+ mDisplaySize.set(width, height);
+ if (mAppBounds.isEmpty()) {
+ mAppBounds.set(0, 0, width, height);
+ }
+ return this;
+ }
+
+ TaskMatrixVerifier withInsets(Rect insets) {
+ mDisplayInsets.set(insets);
+ mLauncherInsets.set(insets);
+ return this;
+ }
+
+ TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
+ mAppBounds.set(bounds);
+ mAppInsets = insets;
+ mAppRotation = appRotation;
+ return this;
+ }
+
+ void verifyNoTransforms() {
+ LauncherModelHelper helper = new LauncherModelHelper();
+ try {
+ helper.sandboxContext.allow(SystemUiProxy.INSTANCE);
+ helper.sandboxContext.allow(SysUINavigationMode.INSTANCE);
+
+ Display display = mock(Display.class);
+ doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
+ doReturn(mDisplaySize.x > mDisplaySize.y ? Surface.ROTATION_90 : Surface.ROTATION_0)
+ .when(display).getRotation();
+ doAnswer(i -> {
+ ((Point) i.getArgument(0)).set(mDisplaySize.x, mDisplaySize.y);
+ return null;
+ }).when(display).getRealSize(any());
+ doAnswer(i -> {
+ Point smallestSize = i.getArgument(0);
+ Point largestSize = i.getArgument(1);
+ smallestSize.x = smallestSize.y = Math.min(mDisplaySize.x, mDisplaySize.y);
+ largestSize.x = largestSize.y = Math.max(mDisplaySize.x, mDisplaySize.y);
+
+ smallestSize.x -= mDisplayInsets.left + mDisplayInsets.right;
+ largestSize.x -= mDisplayInsets.left + mDisplayInsets.right;
+
+ smallestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom;
+ largestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom;
+ return null;
+ }).when(display).getCurrentSizeRange(any(), any());
+ DisplayController.Info mockInfo = new Info(helper.sandboxContext, display);
+
+ DisplayController controller =
+ DisplayController.INSTANCE.get(helper.sandboxContext);
+ controller.close();
+ ReflectionHelpers.setField(controller, "mInfo", mockInfo);
+
+ mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext)
+ .getBestMatch(mAppBounds.width(), mAppBounds.height());
+ mDeviceProfile.updateInsets(mLauncherInsets);
+
+ TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext,
+ FallbackActivityInterface.INSTANCE);
+ tvs.setDp(mDeviceProfile);
+
+ int launcherRotation = mockInfo.rotation;
+ if (mAppRotation < 0) {
+ mAppRotation = launcherRotation;
+ }
+
+ tvs.getOrientationState().update(launcherRotation, mAppRotation);
+ if (mAppInsets == null) {
+ mAppInsets = new Rect(mLauncherInsets);
+ }
+ tvs.setPreviewBounds(mAppBounds, mAppInsets);
+
+ tvs.fullScreenProgress.value = 1;
+ tvs.recentsViewScale.value = tvs.getFullScreenScale();
+ tvs.apply(this);
+ } finally {
+ helper.destroy();
+ }
+ }
+
+ @Override
+ public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+ SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+ proxy.onBuildTargetParams(builder, mock(RemoteAnimationTargetCompat.class), this);
+ return new SurfaceParams[] {builder.build()};
+ }
+
+ @Override
+ public void applySurfaceParams(SurfaceParams[] params) {
+ // Verify that the task position remains the same
+ RectF newAppBounds = new RectF(mAppBounds);
+ params[0].matrix.mapRect(newAppBounds);
+ Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
+
+ System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
+ }
+ }
+
+ private static class AlmostSame extends TypeSafeMatcher<RectF> {
+
+ // Allow .1% error margin to account for float to int conversions
+ private final float mErrorFactor = .001f;
+ private final Rect mExpected;
+
+ AlmostSame(Rect expected) {
+ mExpected = expected;
+ }
+
+ @Override
+ protected boolean matchesSafely(RectF item) {
+ float errorWidth = mErrorFactor * mExpected.width();
+ float errorHeight = mErrorFactor * mExpected.height();
+ return Math.abs(item.left - mExpected.left) < errorWidth
+ && Math.abs(item.top - mExpected.top) < errorHeight
+ && Math.abs(item.right - mExpected.right) < errorWidth
+ && Math.abs(item.bottom - mExpected.bottom) < errorHeight;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(mExpected);
+ }
+ }
+}
diff --git a/res/color-v31/overview_scrim_dark.xml b/res/color-v31/overview_scrim_dark.xml
index b8ed774..85ede9a 100644
--- a/res/color-v31/overview_scrim_dark.xml
+++ b/res/color-v31/overview_scrim_dark.xml
@@ -14,5 +14,5 @@
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_800" />
+ <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
</selector>
diff --git a/res/drawable-v28/widgets_bottom_sheet_background.xml b/res/drawable-v28/widgets_bottom_sheet_background.xml
deleted file mode 100644
index 7fb8681..0000000
--- a/res/drawable-v28/widgets_bottom_sheet_background.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/surface" />
- <corners
- android:topLeftRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"
- />
-</shape>
\ No newline at end of file
diff --git a/res/drawable/widgets_bottom_sheet_background.xml b/res/drawable/bg_widgets_full_sheet.xml
similarity index 65%
rename from res/drawable/widgets_bottom_sheet_background.xml
rename to res/drawable/bg_widgets_full_sheet.xml
index b877546..dfcd354 100644
--- a/res/drawable/widgets_bottom_sheet_background.xml
+++ b/res/drawable/bg_widgets_full_sheet.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
+<!-- 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.
@@ -14,13 +13,11 @@
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/surface" />
+ android:shape="rectangle" >
+ <solid android:color="?android:attr/colorBackground" />
<corners
- android:topLeftRadius="@dimen/default_dialog_corner_radius"
- android:topRightRadius="@dimen/default_dialog_corner_radius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"
- />
+ android:topLeftRadius="@dimen/dialogCornerRadius"
+ android:topRightRadius="@dimen/dialogCornerRadius" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_picker_handle.xml b/res/drawable/bg_widgets_picker_handle.xml
deleted file mode 100644
index 68681a6..0000000
--- a/res/drawable/bg_widgets_picker_handle.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <shape android:shape="rectangle">
- <solid android:color="?android:attr/colorBackground" />
- <padding android:top="16dp"/>
- </shape>
- </item>
- <item android:gravity="center">
- <shape android:shape="rectangle">
- <solid android:color="?android:attr/textColorSecondary" />
- <size android:width="48dp" android:height="2dp" />
- </shape>
- </item>
-</layer-list>
\ No newline at end of file
diff --git a/res/drawable/gesture_tutorial_motion_overview_light_mode.xml b/res/drawable/gesture_tutorial_motion_overview_light_mode.xml
deleted file mode 100644
index 75887c9..0000000
--- a/res/drawable/gesture_tutorial_motion_overview_light_mode.xml
+++ /dev/null
@@ -1,1587 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
- <target android:name="_R_G_L_4_G_N_3_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="1050"
- android:pathData="M 206,446C 206,446 206,395 206,395"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="217">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_4_G_N_3_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="1050"
- android:propertyName="scaleX"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.6"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="1050"
- android:propertyName="scaleY"
- android:startOffset="217"
- android:valueFrom="1"
- android:valueTo="0.6"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_4_G_N_3_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="3400"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_27_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_26_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_25_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_24_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_23_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_22_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_21_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_20_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_19_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_18_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_17_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_16_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_15_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_14_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_13_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_12_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_11_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_10_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_9_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_8_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_7_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_6_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_5_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_4_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_3_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_2_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_L_0_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:pathData="M 206,395C 206,403.5 206,437.5 206,446"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="2083">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleX"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleX"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleY"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_3_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0"
- android:valueTo="0.6"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_D_0_P_0_G_0_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleX"
- android:startOffset="2567"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleY"
- android:startOffset="2567"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:pathData="M 206,395C 206,403.5 206,437.5 206,446"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="2083">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleX"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleX"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleY"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_2_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="2567"
- android:valueFrom="0"
- android:valueTo="0.6"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="250"
- android:pathData="M -556.176,-7.307C -556.176,-7.307 -421.176,-7.307 -421.176,-7.307"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="1350">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.272,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="417"
- android:pathData="M -421.176,-7.307C -421.176,-7.307 -429.51,-7.307 -429.51,-7.307"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="1600">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:pathData="M 206,395C 206,403.5 206,437.5 206,446"
- android:propertyName="translateXY"
- android:propertyXName="translateX"
- android:propertyYName="translateY"
- android:startOffset="2083">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleX"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="350"
- android:propertyName="scaleY"
- android:startOffset="2083"
- android:valueFrom="0.6"
- android:valueTo="0.72718"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.34,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleX"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="217"
- android:propertyName="scaleY"
- android:startOffset="2433"
- android:valueFrom="0.72718"
- android:valueTo="0.72"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.51,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_N_2_T_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="0"
- android:propertyName="scaleY"
- android:startOffset="1350"
- android:valueFrom="0"
- android:valueTo="0.6"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="fillAlpha"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="1833"
- android:propertyName="fillAlpha"
- android:startOffset="217"
- android:valueFrom="0.75"
- android:valueTo="0.75"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="fillAlpha"
- android:startOffset="2050"
- android:valueFrom="0.75"
- android:valueTo="0"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_0_G_D_0_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="217"
- android:propertyName="pathData"
- android:startOffset="0"
- android:valueFrom="M0 406 C21.54,406 39,423.46 39,445 C39,466.54 21.54,484 0,484 C-21.54,484 -39,466.54 -39,445 C-39,423.46 -21.54,406 0,406c "
- android:valueTo="M0 395 C27.61,395 50,417.39 50,445 C50,472.61 27.61,495 0,495 C-27.61,495 -50,472.61 -50,445 C-50,417.39 -27.61,395 0,395c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="1050"
- android:propertyName="pathData"
- android:startOffset="217"
- android:valueFrom="M0 395 C27.61,395 50,417.39 50,445 C50,472.61 27.61,495 0,495 C-27.61,495 -50,472.61 -50,445 C-50,417.39 -27.61,395 0,395c "
- android:valueTo="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.5,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="783"
- android:propertyName="pathData"
- android:startOffset="1267"
- android:valueFrom="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueTo="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="pathData"
- android:startOffset="2050"
- android:valueFrom="M0 166 C27.61,166 50,188.39 50,216 C50,243.61 27.61,266 0,266 C-27.61,266 -50,243.61 -50,216 C-50,188.39 -27.61,166 0,166c "
- android:valueTo="M0 180 C19.88,180 36,196.12 36,216 C36,235.88 19.88,252 0,252 C-19.88,252 -36,235.88 -36,216 C-36,196.12 -19.88,180 0,180c "
- android:valueType="pathType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="time_group">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="2750"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
- </set>
- </aapt:attr>
- </target>
- <aapt:attr name="android:drawable">
- <vector
- android:width="412dp"
- android:height="892dp"
- android:viewportHeight="892"
- android:viewportWidth="412">
- <group android:name="_R_G">
- <group
- android:name="_R_G_L_5_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/fake_wallpaper_color_light_mode"
- android:fillType="nonZero"
- android:pathData=" M206 -446 C206,-446 206,446 206,446 C206,446 -206,446 -206,446 C-206,446 -206,-446 -206,-446 C-206,-446 206,-446 206,-446c " />
- </group>
- <group
- android:name="_R_G_L_4_G_N_3_T_0"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="206"
- android:translateY="446">
- <group
- android:name="_R_G_L_4_G"
- android:translateX="-206"
- android:translateY="-446">
- <group android:name="_R_G_L_4_G_L_0_G">
- <group
- android:name="_R_G_L_4_G_L_0_G_L_27_G"
- android:translateX="206"
- android:translateY="422.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_27_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -395.5 C206,-395.5 206,395.5 206,395.5 C206,395.5 -206,395.5 -206,395.5 C-206,395.5 -206,-395.5 -206,-395.5 C-206,-395.5 206,-395.5 206,-395.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_26_G"
- android:translateX="206"
- android:translateY="496.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_26_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -377.5 C206,-377.5 206,377.5 206,377.5 C206,387.43 197.93,395.5 188,395.5 C188,395.5 -188,395.5 -188,395.5 C-197.93,395.5 -206,387.43 -206,377.5 C-206,377.5 -206,-377.5 -206,-377.5 C-206,-387.43 -197.93,-395.5 -188,-395.5 C-188,-395.5 188,-395.5 188,-395.5 C197.93,-395.5 206,-387.43 206,-377.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_25_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_25_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -23.5 C206,-23.5 206,50.5 206,50.5 C206,50.5 -206,50.5 -206,50.5 C-206,50.5 -206,-23.5 -206,-23.5 C-206,-23.5 206,-23.5 206,-23.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_24_G"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_24_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_23_G"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_23_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_22_G"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_22_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_21_G"
- android:translateX="148.5"
- android:translateY="148">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_21_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M46.5 -5 C46.5,-5 46.5,5 46.5,5 C46.5,7.21 44.71,9 42.5,9 C42.5,9 -42.5,9 -42.5,9 C-44.71,9 -46.5,7.21 -46.5,5 C-46.5,5 -46.5,-5 -46.5,-5 C-46.5,-7.21 -44.71,-9 -42.5,-9 C-42.5,-9 42.5,-9 42.5,-9 C44.71,-9 46.5,-7.21 46.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_20_G"
- android:translateX="186"
- android:translateY="169">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_20_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M84 -4 C84,-4 84,4 84,4 C84,6.21 82.21,8 80,8 C80,8 -80,8 -80,8 C-82.21,8 -84,6.21 -84,4 C-84,4 -84,-4 -84,-4 C-84,-6.21 -82.21,-8 -80,-8 C-80,-8 80,-8 80,-8 C82.21,-8 84,-6.21 84,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_19_G"
- android:translateX="54"
- android:translateY="245">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_19_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_18_G"
- android:translateX="162"
- android:translateY="236">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_18_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60 -5 C60,-5 60,5 60,5 C60,7.21 58.21,9 56,9 C56,9 -56,9 -56,9 C-58.21,9 -60,7.21 -60,5 C-60,5 -60,-5 -60,-5 C-60,-7.21 -58.21,-9 -56,-9 C-56,-9 56,-9 56,-9 C58.21,-9 60,-7.21 60,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_17_G"
- android:translateX="171.5"
- android:translateY="257">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_17_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M69.5 -4 C69.5,-4 69.5,4 69.5,4 C69.5,6.21 67.71,8 65.5,8 C65.5,8 -65.5,8 -65.5,8 C-67.71,8 -69.5,6.21 -69.5,4 C-69.5,4 -69.5,-4 -69.5,-4 C-69.5,-6.21 -67.71,-8 -65.5,-8 C-65.5,-8 65.5,-8 65.5,-8 C67.71,-8 69.5,-6.21 69.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_16_G"
- android:translateX="54"
- android:translateY="333">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_16_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_15_G"
- android:translateX="158"
- android:translateY="324">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_15_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M56 -5 C56,-5 56,5 56,5 C56,7.21 54.21,9 52,9 C52,9 -52,9 -52,9 C-54.21,9 -56,7.21 -56,5 C-56,5 -56,-5 -56,-5 C-56,-7.21 -54.21,-9 -52,-9 C-52,-9 52,-9 52,-9 C54.21,-9 56,-7.21 56,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_14_G"
- android:translateX="217.5"
- android:translateY="345">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_14_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M115.5 -4 C115.5,-4 115.5,4 115.5,4 C115.5,6.21 113.71,8 111.5,8 C111.5,8 -111.5,8 -111.5,8 C-113.71,8 -115.5,6.21 -115.5,4 C-115.5,4 -115.5,-4 -115.5,-4 C-115.5,-6.21 -113.71,-8 -111.5,-8 C-111.5,-8 111.5,-8 111.5,-8 C113.71,-8 115.5,-6.21 115.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_13_G"
- android:translateX="54"
- android:translateY="421">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_13_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_12_G"
- android:translateX="170"
- android:translateY="412">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_12_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M68 -5 C68,-5 68,5 68,5 C68,7.21 66.21,9 64,9 C64,9 -64,9 -64,9 C-66.21,9 -68,7.21 -68,5 C-68,5 -68,-5 -68,-5 C-68,-7.21 -66.21,-9 -64,-9 C-64,-9 64,-9 64,-9 C66.21,-9 68,-7.21 68,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_11_G"
- android:translateX="198.5"
- android:translateY="433">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_10_G"
- android:translateX="54"
- android:translateY="509">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_9_G"
- android:translateX="135"
- android:translateY="500">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M33 -5 C33,-5 33,5 33,5 C33,7.21 31.21,9 29,9 C29,9 -29,9 -29,9 C-31.21,9 -33,7.21 -33,5 C-33,5 -33,-5 -33,-5 C-33,-7.21 -31.21,-9 -29,-9 C-29,-9 29,-9 29,-9 C31.21,-9 33,-7.21 33,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_8_G"
- android:translateX="185.5"
- android:translateY="521">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M83.5 -4 C83.5,-4 83.5,4 83.5,4 C83.5,6.21 81.71,8 79.5,8 C79.5,8 -79.5,8 -79.5,8 C-81.71,8 -83.5,6.21 -83.5,4 C-83.5,4 -83.5,-4 -83.5,-4 C-83.5,-6.21 -81.71,-8 -79.5,-8 C-79.5,-8 79.5,-8 79.5,-8 C81.71,-8 83.5,-6.21 83.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_7_G"
- android:translateX="54"
- android:translateY="597">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_6_G"
- android:translateX="168.5"
- android:translateY="588">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M66.5 -5 C66.5,-5 66.5,5 66.5,5 C66.5,7.21 64.71,9 62.5,9 C62.5,9 -62.5,9 -62.5,9 C-64.71,9 -66.5,7.21 -66.5,5 C-66.5,5 -66.5,-5 -66.5,-5 C-66.5,-7.21 -64.71,-9 -62.5,-9 C-62.5,-9 62.5,-9 62.5,-9 C64.71,-9 66.5,-7.21 66.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_5_G"
- android:translateX="198.5"
- android:translateY="609">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_4_G"
- android:translateX="54"
- android:translateY="685">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_3_G"
- android:translateX="162.5"
- android:translateY="676">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60.5 -5 C60.5,-5 60.5,5 60.5,5 C60.5,7.21 58.71,9 56.5,9 C56.5,9 -56.5,9 -56.5,9 C-58.71,9 -60.5,7.21 -60.5,5 C-60.5,5 -60.5,-5 -60.5,-5 C-60.5,-7.21 -58.71,-9 -56.5,-9 C-56.5,-9 56.5,-9 56.5,-9 C58.71,-9 60.5,-7.21 60.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_2_G"
- android:translateX="174"
- android:translateY="697">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M72 -4 C72,-4 72,4 72,4 C72,6.21 70.21,8 68,8 C68,8 -68,8 -68,8 C-70.21,8 -72,6.21 -72,4 C-72,4 -72,-4 -72,-4 C-72,-6.21 -70.21,-8 -68,-8 C-68,-8 68,-8 68,-8 C70.21,-8 72,-6.21 72,-4c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_1_G"
- android:translateX="313.5"
- android:translateY="798">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M74.5 0 C74.5,0 74.5,0 74.5,0 C74.5,15.45 61.95,28 46.5,28 C46.5,28 -46.5,28 -46.5,28 C-61.95,28 -74.5,15.45 -74.5,0 C-74.5,0 -74.5,0 -74.5,0 C-74.5,-15.45 -61.95,-28 -46.5,-28 C-46.5,-28 46.5,-28 46.5,-28 C61.95,-28 74.5,-15.45 74.5,0c " />
- </group>
- <group
- android:name="_R_G_L_4_G_L_0_G_L_0_G"
- android:translateX="205.5"
- android:translateY="61">
- <path
- android:name="_R_G_L_4_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#f8f9fa"
- android:fillType="nonZero"
- android:pathData=" M171.5 -14 C171.5,-14 171.5,14 171.5,14 C171.5,16.21 169.71,18 167.5,18 C167.5,18 -167.5,18 -167.5,18 C-169.71,18 -171.5,16.21 -171.5,14 C-171.5,14 -171.5,-14 -171.5,-14 C-171.5,-16.21 -169.71,-18 -167.5,-18 C-167.5,-18 167.5,-18 167.5,-18 C169.71,-18 171.5,-16.21 171.5,-14c " />
- </group>
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_3_G_N_2_T_0"
- android:scaleX="0.6"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="395">
- <group
- android:name="_R_G_L_3_G"
- android:translateX="-206"
- android:translateY="-446">
- <group
- android:name="_R_G_L_3_G_L_0_G"
- android:scaleY="0">
- <group
- android:name="_R_G_L_3_G_L_0_G_L_27_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="422.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_27_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -395.5 C206,-395.5 206,395.5 206,395.5 C206,395.5 -206,395.5 -206,395.5 C-206,395.5 -206,-395.5 -206,-395.5 C-206,-395.5 206,-395.5 206,-395.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_26_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="496.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_26_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#dadce0"
- android:fillType="nonZero"
- android:pathData=" M206 -377.5 C206,-377.5 206,377.5 206,377.5 C206,387.43 197.93,395.5 188,395.5 C188,395.5 -188,395.5 -188,395.5 C-197.93,395.5 -206,387.43 -206,377.5 C-206,377.5 -206,-377.5 -206,-377.5 C-206,-387.43 -197.93,-395.5 -188,-395.5 C-188,-395.5 188,-395.5 188,-395.5 C197.93,-395.5 206,-387.43 206,-377.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_25_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_25_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -23.5 C206,-23.5 206,50.5 206,50.5 C206,50.5 -206,50.5 -206,50.5 C-206,50.5 -206,-23.5 -206,-23.5 C-206,-23.5 206,-23.5 206,-23.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_24_G"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="50.5">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_24_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#e8eaed"
- android:fillType="nonZero"
- android:pathData=" M206 -32.5 C206,-32.5 206,32.5 206,32.5 C206,42.43 197.93,50.5 188,50.5 C188,50.5 -188,50.5 -188,50.5 C-197.93,50.5 -206,42.43 -206,32.5 C-206,32.5 -206,-32.5 -206,-32.5 C-206,-42.43 -197.93,-50.5 -188,-50.5 C-188,-50.5 188,-50.5 188,-50.5 C197.93,-50.5 206,-42.43 206,-32.5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_23_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_23_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_22_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="157">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_22_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_21_G"
- android:scaleY="0"
- android:translateX="148.5"
- android:translateY="148">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_21_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M46.5 -5 C46.5,-5 46.5,5 46.5,5 C46.5,7.21 44.71,9 42.5,9 C42.5,9 -42.5,9 -42.5,9 C-44.71,9 -46.5,7.21 -46.5,5 C-46.5,5 -46.5,-5 -46.5,-5 C-46.5,-7.21 -44.71,-9 -42.5,-9 C-42.5,-9 42.5,-9 42.5,-9 C44.71,-9 46.5,-7.21 46.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_20_G"
- android:scaleY="0"
- android:translateX="186"
- android:translateY="169">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_20_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M84 -4 C84,-4 84,4 84,4 C84,6.21 82.21,8 80,8 C80,8 -80,8 -80,8 C-82.21,8 -84,6.21 -84,4 C-84,4 -84,-4 -84,-4 C-84,-6.21 -82.21,-8 -80,-8 C-80,-8 80,-8 80,-8 C82.21,-8 84,-6.21 84,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_19_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="245">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_19_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_18_G"
- android:scaleY="0"
- android:translateX="162"
- android:translateY="236">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_18_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60 -5 C60,-5 60,5 60,5 C60,7.21 58.21,9 56,9 C56,9 -56,9 -56,9 C-58.21,9 -60,7.21 -60,5 C-60,5 -60,-5 -60,-5 C-60,-7.21 -58.21,-9 -56,-9 C-56,-9 56,-9 56,-9 C58.21,-9 60,-7.21 60,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_17_G"
- android:scaleY="0"
- android:translateX="171.5"
- android:translateY="257">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_17_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M69.5 -4 C69.5,-4 69.5,4 69.5,4 C69.5,6.21 67.71,8 65.5,8 C65.5,8 -65.5,8 -65.5,8 C-67.71,8 -69.5,6.21 -69.5,4 C-69.5,4 -69.5,-4 -69.5,-4 C-69.5,-6.21 -67.71,-8 -65.5,-8 C-65.5,-8 65.5,-8 65.5,-8 C67.71,-8 69.5,-6.21 69.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_16_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="333">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_16_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_15_G"
- android:scaleY="0"
- android:translateX="158"
- android:translateY="324">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_15_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M56 -5 C56,-5 56,5 56,5 C56,7.21 54.21,9 52,9 C52,9 -52,9 -52,9 C-54.21,9 -56,7.21 -56,5 C-56,5 -56,-5 -56,-5 C-56,-7.21 -54.21,-9 -52,-9 C-52,-9 52,-9 52,-9 C54.21,-9 56,-7.21 56,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_14_G"
- android:scaleY="0"
- android:translateX="217.5"
- android:translateY="345">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_14_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M115.5 -4 C115.5,-4 115.5,4 115.5,4 C115.5,6.21 113.71,8 111.5,8 C111.5,8 -111.5,8 -111.5,8 C-113.71,8 -115.5,6.21 -115.5,4 C-115.5,4 -115.5,-4 -115.5,-4 C-115.5,-6.21 -113.71,-8 -111.5,-8 C-111.5,-8 111.5,-8 111.5,-8 C113.71,-8 115.5,-6.21 115.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_13_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="421">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_13_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_12_G"
- android:scaleY="0"
- android:translateX="170"
- android:translateY="412">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_12_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M68 -5 C68,-5 68,5 68,5 C68,7.21 66.21,9 64,9 C64,9 -64,9 -64,9 C-66.21,9 -68,7.21 -68,5 C-68,5 -68,-5 -68,-5 C-68,-7.21 -66.21,-9 -64,-9 C-64,-9 64,-9 64,-9 C66.21,-9 68,-7.21 68,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_11_G"
- android:scaleY="0"
- android:translateX="198.5"
- android:translateY="433">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_11_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_10_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="509">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_10_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_9_G"
- android:scaleY="0"
- android:translateX="135"
- android:translateY="500">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_9_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M33 -5 C33,-5 33,5 33,5 C33,7.21 31.21,9 29,9 C29,9 -29,9 -29,9 C-31.21,9 -33,7.21 -33,5 C-33,5 -33,-5 -33,-5 C-33,-7.21 -31.21,-9 -29,-9 C-29,-9 29,-9 29,-9 C31.21,-9 33,-7.21 33,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_8_G"
- android:scaleY="0"
- android:translateX="185.5"
- android:translateY="521">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_8_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M83.5 -4 C83.5,-4 83.5,4 83.5,4 C83.5,6.21 81.71,8 79.5,8 C79.5,8 -79.5,8 -79.5,8 C-81.71,8 -83.5,6.21 -83.5,4 C-83.5,4 -83.5,-4 -83.5,-4 C-83.5,-6.21 -81.71,-8 -79.5,-8 C-79.5,-8 79.5,-8 79.5,-8 C81.71,-8 83.5,-6.21 83.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_7_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="597">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_7_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_6_G"
- android:scaleY="0"
- android:translateX="168.5"
- android:translateY="588">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_6_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M66.5 -5 C66.5,-5 66.5,5 66.5,5 C66.5,7.21 64.71,9 62.5,9 C62.5,9 -62.5,9 -62.5,9 C-64.71,9 -66.5,7.21 -66.5,5 C-66.5,5 -66.5,-5 -66.5,-5 C-66.5,-7.21 -64.71,-9 -62.5,-9 C-62.5,-9 62.5,-9 62.5,-9 C64.71,-9 66.5,-7.21 66.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_5_G"
- android:scaleY="0"
- android:translateX="198.5"
- android:translateY="609">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_5_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M96.5 -4 C96.5,-4 96.5,4 96.5,4 C96.5,6.21 94.71,8 92.5,8 C92.5,8 -92.5,8 -92.5,8 C-94.71,8 -96.5,6.21 -96.5,4 C-96.5,4 -96.5,-4 -96.5,-4 C-96.5,-6.21 -94.71,-8 -92.5,-8 C-92.5,-8 92.5,-8 92.5,-8 C94.71,-8 96.5,-6.21 96.5,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_4_G"
- android:scaleY="0"
- android:translateX="54"
- android:translateY="685">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_4_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M28 0 C28,15.46 15.46,28 0,28 C-15.46,28 -28,15.46 -28,0 C-28,-15.46 -15.46,-28 0,-28 C15.46,-28 28,-15.46 28,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_3_G"
- android:scaleY="0"
- android:translateX="162.5"
- android:translateY="676">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_3_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M60.5 -5 C60.5,-5 60.5,5 60.5,5 C60.5,7.21 58.71,9 56.5,9 C56.5,9 -56.5,9 -56.5,9 C-58.71,9 -60.5,7.21 -60.5,5 C-60.5,5 -60.5,-5 -60.5,-5 C-60.5,-7.21 -58.71,-9 -56.5,-9 C-56.5,-9 56.5,-9 56.5,-9 C58.71,-9 60.5,-7.21 60.5,-5c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_2_G"
- android:scaleY="0"
- android:translateX="174"
- android:translateY="697">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M72 -4 C72,-4 72,4 72,4 C72,6.21 70.21,8 68,8 C68,8 -68,8 -68,8 C-70.21,8 -72,6.21 -72,4 C-72,4 -72,-4 -72,-4 C-72,-6.21 -70.21,-8 -68,-8 C-68,-8 68,-8 68,-8 C70.21,-8 72,-6.21 72,-4c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_1_G"
- android:scaleY="0"
- android:translateX="313.5"
- android:translateY="798">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#bdc1c6"
- android:fillType="nonZero"
- android:pathData=" M74.5 0 C74.5,0 74.5,0 74.5,0 C74.5,15.45 61.95,28 46.5,28 C46.5,28 -46.5,28 -46.5,28 C-61.95,28 -74.5,15.45 -74.5,0 C-74.5,0 -74.5,0 -74.5,0 C-74.5,-15.45 -61.95,-28 -46.5,-28 C-46.5,-28 46.5,-28 46.5,-28 C61.95,-28 74.5,-15.45 74.5,0c " />
- </group>
- <group
- android:name="_R_G_L_3_G_L_0_G_L_0_G"
- android:scaleY="0"
- android:translateX="205.5"
- android:translateY="61">
- <path
- android:name="_R_G_L_3_G_L_0_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#f8f9fa"
- android:fillType="nonZero"
- android:pathData=" M171.5 -14 C171.5,-14 171.5,14 171.5,14 C171.5,16.21 169.71,18 167.5,18 C167.5,18 -167.5,18 -167.5,18 C-169.71,18 -171.5,16.21 -171.5,14 C-171.5,14 -171.5,-14 -171.5,-14 C-171.5,-16.21 -169.71,-18 -167.5,-18 C-167.5,-18 167.5,-18 167.5,-18 C169.71,-18 171.5,-16.21 171.5,-14c " />
- </group>
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_2_G_N_2_T_0"
- android:scaleX="0.6"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="395">
- <group
- android:name="_R_G_L_2_G"
- android:scaleX="1.3767699999999998"
- android:scaleY="1.3767699999999998"
- android:translateY="-508.163">
- <group
- android:name="_R_G_L_2_G_D_0_P_0_G_0_T_0"
- android:scaleX="0"
- android:scaleY="0">
- <path
- android:name="_R_G_L_2_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="#9aa0a6"
- android:fillType="nonZero"
- android:pathData=" M0 25 C13.81,25 25,13.81 25,0 C25,-13.81 13.81,-25 0,-25 C-13.81,-25 -25,-13.81 -25,0 C-25,13.81 -13.81,25 0,25c " />
- </group>
- </group>
- </group>
- <group
- android:name="_R_G_L_1_G_N_2_T_0"
- android:scaleX="0.6"
- android:scaleY="0"
- android:translateX="206"
- android:translateY="395">
- <group
- android:name="_R_G_L_1_G"
- android:scaleX="1.39"
- android:scaleY="1.39"
- android:translateX="-556.176"
- android:translateY="-7.307">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/gesture_tutorial_fake_previous_task_view_color"
- android:fillType="nonZero"
- android:pathData=" M135 -301 C135,-301 135,311 135,311 C135,319.28 128.28,326 120,326 C120,326 -120,326 -120,326 C-128.28,326 -135,319.28 -135,311 C-135,311 -135,-301 -135,-301 C-135,-309.28 -128.28,-316 -120,-316 C-120,-316 120,-316 120,-316 C128.28,-316 135,-309.28 135,-301c " />
- </group>
- </group>
- <group
- android:name="_R_G_L_0_G"
- android:translateX="206"
- android:translateY="446">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="0"
- android:fillColor="#84ba69"
- android:fillType="nonZero"
- android:pathData=" M0 406 C21.54,406 39,423.46 39,445 C39,466.54 21.54,484 0,484 C-21.54,484 -39,466.54 -39,445 C-39,423.46 -21.54,406 0,406c " />
- </group>
- </group>
- <group android:name="time_group" />
- </vector>
- </aapt:attr>
-</animated-vector>
\ No newline at end of file
diff --git a/res/drawable/padded_rounded_action_button.xml b/res/drawable/padded_rounded_action_button.xml
index 6432efd..6863f92 100644
--- a/res/drawable/padded_rounded_action_button.xml
+++ b/res/drawable/padded_rounded_action_button.xml
@@ -14,18 +14,11 @@
~ limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
+ android:drawable="@drawable/rounded_action_button"
android:left="@dimen/padded_rounded_button_padding"
android:top="@dimen/padded_rounded_button_padding"
android:right="@dimen/padded_rounded_button_padding"
- android:bottom="@dimen/padded_rounded_button_padding">
- <shape android:shape="rectangle">
- <corners android:radius="@dimen/rounded_button_radius" />
- <stroke android:width="1dp"
- android:color="?androidprv:attr/colorAccentPrimaryVariant" />
- </shape>
- </item>
+ android:bottom="@dimen/padded_rounded_button_padding" />
</layer-list>
-
diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml
index f043893..b9942c0 100644
--- a/res/drawable/rounded_action_button.xml
+++ b/res/drawable/rounded_action_button.xml
@@ -19,7 +19,9 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/rounded_button_radius" />
- <stroke android:width="1dp" android:color="@color/all_apps_tab_background_selected" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/colorAccentPrimaryVariant" />
<padding
android:left="@dimen/rounded_button_padding"
android:right="@dimen/rounded_button_padding" />
diff --git a/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
index 9a6f8c3..88a92eb 100644
--- a/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -27,15 +27,13 @@
android:gravity="center"
android:padding="16dp"
android:background="@drawable/arrow_toast_rounded_background"
- android:elevation="2dp"
- android:outlineProvider="none"
+ android:elevation="@dimen/arrow_toast_elevation"
android:textColor="@color/arrow_tip_view_content"
android:textSize="14sp"/>
<View
android:id="@+id/arrow"
- android:elevation="2dp"
- android:outlineProvider="none"
+ android:elevation="@dimen/arrow_toast_elevation"
android:layout_width="@dimen/arrow_toast_arrow_width"
android:layout_height="10dp"/>
</merge>
diff --git a/res/layout/launcher_preview_two_panel_layout.xml b/res/layout/launcher_preview_two_panel_layout.xml
index 7b227e0..f76fc5a 100644
--- a/res/layout/launcher_preview_two_panel_layout.xml
+++ b/res/layout/launcher_preview_two_panel_layout.xml
@@ -25,24 +25,24 @@
android:layout_height="match_parent">
<com.android.launcher3.CellLayout
- android:id="@+id/workspace_left"
+ android:id="@+id/workspace"
android:layout_width="0dp"
android:layout_height="0dp"
android:theme="@style/HomeScreenElementTheme"
launcher:containerType="workspace"
launcher:layout_constraintStart_toStartOf="parent"
launcher:layout_constraintTop_toTopOf="parent"
- launcher:layout_constraintEnd_toStartOf="@id/workspace"
+ launcher:layout_constraintEnd_toStartOf="@id/workspace_right"
launcher:layout_constraintBottom_toBottomOf="parent"
launcher:pageIndicator="@+id/page_indicator" />
<com.android.launcher3.CellLayout
- android:id="@+id/workspace"
+ android:id="@+id/workspace_right"
android:layout_width="0dp"
android:layout_height="0dp"
android:theme="@style/HomeScreenElementTheme"
launcher:containerType="workspace"
- launcher:layout_constraintStart_toEndOf="@id/workspace_left"
+ launcher:layout_constraintStart_toEndOf="@id/workspace"
launcher:layout_constraintTop_toTopOf="parent"
launcher:layout_constraintEnd_toEndOf="parent"
launcher:layout_constraintBottom_toBottomOf="parent"
diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml
index 84822a6..91897e9 100644
--- a/res/layout/notification_content.xml
+++ b/res/layout/notification_content.xml
@@ -14,10 +14,11 @@
limitations under the License.
-->
-<merge
+<com.android.launcher3.notification.NotificationMainView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
<!-- header -->
<FrameLayout
@@ -49,7 +50,7 @@
</FrameLayout>
<!-- Main view -->
- <com.android.launcher3.notification.NotificationMainView
+ <FrameLayout
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -59,7 +60,6 @@
android:id="@+id/text_and_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/popupColorPrimary"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingTop="@dimen/notification_padding"
@@ -95,5 +95,5 @@
android:layout_marginTop="@dimen/notification_padding"
android:layout_marginStart="@dimen/notification_icon_padding" />
- </com.android.launcher3.notification.NotificationMainView>
-</merge>
\ No newline at end of file
+ </FrameLayout>
+</com.android.launcher3.notification.NotificationMainView>
\ No newline at end of file
diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml
index 18014bb..9327287 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -31,12 +31,9 @@
android:elevation="@dimen/deep_shortcuts_elevation"
android:orientation="vertical"/>
- <LinearLayout
+ <com.android.launcher3.notification.NotificationContainer
android:id="@+id/notification_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="gone"
- android:background="?attr/popupColorPrimary"
- android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical"/>
+ android:visibility="gone"/>
</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
diff --git a/res/layout/qsb_preview.xml b/res/layout/qsb_preview.xml
new file mode 100644
index 0000000..801fb04
--- /dev/null
+++ b/res/layout/qsb_preview.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.qsb.QsbContainerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:id="@id/search_container_workspace"
+ android:padding="0dp" >
+
+ <fragment
+ android:name="com.android.launcher3.qsb.QsbContainerView$QsbFragment"
+ android:layout_width="match_parent"
+ android:tag="qsb_view"
+ android:layout_height="match_parent"/>
+</com.android.launcher3.qsb.QsbContainerView>
\ No newline at end of file
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 89895e5..21d532e 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -17,7 +17,8 @@
<com.android.launcher3.shortcuts.DeepShortcutView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/bg_popup_item_width"
- android:layout_height="@dimen/bg_popup_item_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/bg_popup_item_height"
android:elevation="@dimen/deep_shortcuts_elevation"
android:background="@drawable/middle_item_primary"
android:theme="@style/PopupItem" >
diff --git a/res/layout/system_shortcut_content.xml b/res/layout/system_shortcut_content.xml
index 8b39202..3ef0b94 100644
--- a/res/layout/system_shortcut_content.xml
+++ b/res/layout/system_shortcut_content.xml
@@ -22,6 +22,9 @@
android:id="@+id/bubble_text"
android:background="?android:attr/selectableItemBackground"
android:gravity="start|center_vertical"
+ android:paddingTop="@dimen/bg_popup_item_vertical_padding"
+ android:paddingBottom="@dimen/bg_popup_item_vertical_padding"
+ android:minHeight="@dimen/bg_popup_item_height"
android:textAlignment="viewStart"
android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
android:paddingEnd="@dimen/popup_padding_end"
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
index 3d330dc..1a2cfc6 100644
--- a/res/layout/widgets_bottom_sheet_content.xml
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -18,7 +18,7 @@
android:id="@+id/widgets_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/widgets_bottom_sheet_background"
+ android:background="@drawable/bg_rounded_corner_bottom_sheet"
android:paddingTop="16dp"
android:orientation="vertical">
<View
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 96b73c2..8afd40b 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -19,13 +19,21 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:theme="?attr/widgetsTheme" >
+ android:theme="?attr/widgetsTheme">
- <com.android.launcher3.views.TopRoundedCornerView
+ <com.android.launcher3.views.SpringRelativeLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?android:attr/colorBackground">
+ android:background="@drawable/bg_widgets_full_sheet">
+
+ <View
+ android:id="@+id/collapse_handle"
+ android:layout_width="48dp"
+ android:layout_height="2dp"
+ android:layout_marginTop="16dp"
+ android:layout_centerHorizontal="true"
+ android:background="?android:attr/textColorSecondary"/>
<TextView
android:id="@+id/no_widgets_text"
@@ -35,6 +43,7 @@
android:visibility="gone"
android:fontFamily="sans-serif-medium"
android:textSize="20sp"
+ android:layout_below="@id/search_and_recommendations_container"
tools:text="No widgets available" />
<!-- Fast scroller popup -->
@@ -57,9 +66,10 @@
android:id="@+id/search_widgets_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_below="@id/collapse_handle"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:visibility="gone"
android:clipToPadding="false" />
- </com.android.launcher3.views.TopRoundedCornerView>
+ </com.android.launcher3.views.SpringRelativeLayout>
</com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index fefad19..85f14cd 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -22,7 +22,7 @@
android:layout_height="match_parent"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
android:clipToPadding="false"
- android:paddingTop="@dimen/widget_picker_view_pager_top_padding"
+ android:layout_below="@id/collapse_handle"
android:descendantFocusability="afterDescendants"
launcher:pageIndicator="@+id/tabs">
@@ -40,5 +40,84 @@
</com.android.launcher3.workprofile.PersonalWorkPagedView>
- <include layout="@layout/widgets_personal_work_tabs"/>
+ <!-- SearchAndRecommendationsView contains the tab layout as well -->
+ <com.android.launcher3.widget.picker.SearchAndRecommendationsView
+ android:id="@+id/search_and_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:layout_below="@id/collapse_handle"
+ android:paddingBottom="0dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textSize="24sp"
+ android:layout_marginTop="24dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/widget_button_text"/>
+
+ <FrameLayout
+ android:id="@+id/search_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="0.1dp"
+ android:background="?android:attr/colorBackground"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false">
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+ android:id="@+id/recommended_widget_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:background="@drawable/widgets_recommendation_background"
+ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+ android:visibility="gone" />
+
+ <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal"
+ android:paddingVertical="8dp"
+ android:paddingLeft="@dimen/widget_tabs_horizontal_padding"
+ android:paddingRight="@dimen/widget_tabs_horizontal_padding"
+ android:background="?android:attr/colorBackground"
+ style="@style/TextHeadline">
+
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
+ android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
+ android:layout_weight="1"
+ android:background="@drawable/all_apps_tabs_background"
+ android:text="@string/widgets_full_sheet_personal_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp"
+ style="?android:attr/borderlessButtonStyle" />
+
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
+ android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
+ android:layout_weight="1"
+ android:background="@drawable/all_apps_tabs_background"
+ android:text="@string/widgets_full_sheet_work_tab"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp"
+ style="?android:attr/borderlessButtonStyle" />
+ </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
+
+ </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index 0f7e020..dde82ea 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -13,10 +13,54 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.launcher3.widget.picker.WidgetsRecyclerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/primary_widgets_list_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
- android:clipToPadding="false" />
\ No newline at end of file
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.android.launcher3.widget.picker.WidgetsRecyclerView
+ android:id="@+id/primary_widgets_list_view"
+ android:layout_below="@id/collapse_handle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:clipToPadding="false" />
+
+ <!-- SearchAndRecommendationsView without the tab layout as well -->
+ <com.android.launcher3.widget.picker.SearchAndRecommendationsView
+ android:id="@+id/search_and_recommendations_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+ android:layout_below="@id/collapse_handle"
+ android:paddingBottom="16dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textSize="24sp"
+ android:layout_marginTop="24dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/widget_button_text"/>
+
+ <FrameLayout
+ android:id="@+id/search_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="0.1dp"
+ android:background="?android:attr/colorBackground"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false">
+ <include layout="@layout/widgets_search_bar" />
+ </FrameLayout>
+
+ <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+ android:id="@+id/recommended_widget_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:background="@drawable/widgets_recommendation_background"
+ android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+ android:visibility="gone" />
+ </com.android.launcher3.widget.picker.SearchAndRecommendationsView>
+
+</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_search_and_recommendations.xml b/res/layout/widgets_full_sheet_search_and_recommendations.xml
deleted file mode 100644
index 938c8ed..0000000
--- a/res/layout/widgets_full_sheet_search_and_recommendations.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.launcher3.widget.picker.SearchAndRecommendationsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/search_and_recommendations_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
- android:layout_marginBottom="16dp"
- android:orientation="vertical">
-
- <View
- android:id="@+id/collapse_handle"
- android:layout_width="match_parent"
- android:layout_height="18dp"
- android:elevation="0.1dp"
- android:background="@drawable/bg_widgets_picker_handle"/>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textSize="24sp"
- android:layout_marginTop="24dp"
- android:textColor="?android:attr/textColorSecondary"
- android:text="@string/widget_button_text"/>
-
- <FrameLayout
- android:id="@+id/search_bar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:elevation="0.1dp"
- android:background="?android:attr/colorBackground"
- android:paddingBottom="8dp"
- android:clipToPadding="false">
- <include layout="@layout/widgets_search_bar" />
- </FrameLayout>
-
- <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
- android:id="@+id/recommended_widget_table"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:background="@drawable/widgets_recommendation_background"
- android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
- android:visibility="gone" />
-</com.android.launcher3.widget.picker.SearchAndRecommendationsView>
diff --git a/res/layout/widgets_personal_work_tabs.xml b/res/layout/widgets_personal_work_tabs.xml
deleted file mode 100644
index 532c422..0000000
--- a/res/layout/widgets_personal_work_tabs.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
--->
-
-<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tabs"
- android:layout_width="match_parent"
- android:layout_height="@dimen/all_apps_header_pill_height"
- android:gravity="center_horizontal"
- android:orientation="horizontal"
- android:layout_marginHorizontal="@dimen/widget_tabs_horizontal_margin"
- style="@style/TextHeadline">
-
- <Button
- android:id="@+id/tab_personal"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
- android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
- android:layout_weight="1"
- android:background="@drawable/all_apps_tabs_background"
- android:text="@string/widgets_full_sheet_personal_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp"
- style="?android:attr/borderlessButtonStyle" />
-
- <Button
- android:id="@+id/tab_work"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
- android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
- android:layout_weight="1"
- android:background="@drawable/all_apps_tabs_background"
- android:text="@string/widgets_full_sheet_work_tab"
- android:textColor="@color/all_apps_tab_text"
- android:textSize="14sp"
- style="?android:attr/borderlessButtonStyle" />
-</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
\ No newline at end of file
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index ec34b47..79bce70 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -28,7 +28,7 @@
android:layout_marginTop="40dp"
android:text="@string/work_apps_paused_title"
android:textAlignment="center"
- android:textSize="22sp" />
+ android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
diff --git a/res/raw/downgrade_schema.json b/res/raw/downgrade_schema.json
index bc25cec..14eac9f 100644
--- a/res/raw/downgrade_schema.json
+++ b/res/raw/downgrade_schema.json
@@ -2,8 +2,9 @@
// Note: Comments are not supported in JSON schema, but android parser is lenient.
// Maximum DB version supported by this schema
- "version" : 29,
+ "version" : 30,
+ "downgrade_to_29" : [],
"downgrade_to_28" : [
"ALTER TABLE favorites RENAME TO temp_favorites;",
"CREATE TABLE favorites(_id INTEGER PRIMARY KEY, title TEXT, intent TEXT, container INTEGER, screen INTEGER, cellX INTEGER, cellY INTEGER, spanX INTEGER, spanY INTEGER, itemType INTEGER, appWidgetId INTEGER NOT NULL DEFAULT - 1, iconPackage TEXT, iconResource TEXT, icon BLOB, appWidgetProvider TEXT, modified INTEGER NOT NULL DEFAULT 0, restored INTEGER NOT NULL DEFAULT 0, profileId INTEGER DEFAULT 0, rank INTEGER NOT NULL DEFAULT 0, options INTEGER NOT NULL DEFAULT 0);",
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 9bc08e3..a66b9b6 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -120,7 +120,7 @@
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন এপসমূহৰ বাবে"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"অজ্ঞাত"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"আঁতৰাওক"</string>
- <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
+ <string name="abandoned_search" msgid="891119232568284442">"সন্ধান কৰক"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"এই এপটো ইনষ্টল কৰা হোৱা নাই"</string>
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"এই আইকনৰ এপটো ইনষ্টল কৰা হোৱা নাই। আপুনি এইটো আঁতৰাব পাৰে অথবা এপটো বিচাৰি মেনুৱেলভাৱে ইনষ্টল কৰিব পাৰে।"</string>
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হৈছে"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 3fa8ac7..26d1c8c 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -120,7 +120,7 @@
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ಹೊಸ ಅಪ್ಲಿಕೇಶನ್ಗಳಿಗೆ"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"ಅಪರಿಚಿತ"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"ತೆಗೆದುಹಾಕಿ"</string>
- <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
+ <string name="abandoned_search" msgid="891119232568284442">"ಹುಡುಕಿ"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಸ್ಥಾಪನೆಗೊಂಡಿಲ್ಲ"</string>
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ಈ ಐಕಾನ್ ಅಪ್ಲಿಕೇಶನ್ ಸ್ಥಾಪನೆಗೊಂಡಿಲ್ಲ. ನೀವು ಅದನ್ನು ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಅಪ್ಲಿಕೇಶನ್ ಹುಡುಕಬಹುದು ಮತ್ತು ಹಸ್ತಚಾಲಿತವಾಗಿ ಅದನ್ನು ಸ್ಥಾಪಿಸಬಹುದು."</string>
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾಗುತ್ತಿದೆ, <xliff:g id="PROGRESS">%2$s</xliff:g> ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index f282bd1..67b999d 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -120,7 +120,7 @@
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"പുതിയ ആപ്പുകൾക്ക്"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"അജ്ഞാതം"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"നീക്കംചെയ്യുക"</string>
- <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
+ <string name="abandoned_search" msgid="891119232568284442">"തിരയുക"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"ഈ അപ്ലിക്കേഷൻ ഇൻസ്റ്റാളുചെയ്തിട്ടില്ല"</string>
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ഈ ഐക്കണുവേണ്ടി അപ്ലിക്കേഷൻ ഇൻസ്റ്റാളുചെയ്തിട്ടില്ല. നിങ്ങൾക്കത് നീക്കംചെയ്യാനാകും അല്ലെങ്കിൽ അപ്ലിക്കേഷനുവേണ്ടി തിരഞ്ഞുകൊണ്ട് അത് സ്വമേധയാ ഇൻസ്റ്റാളുചെയ്യുക."</string>
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ഇൻസ്റ്റാൾ ചെയ്യുന്നു, <xliff:g id="PROGRESS">%2$s</xliff:g> പൂർത്തിയായി"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index b1d6445..e0f1865 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -120,7 +120,7 @@
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"ନୂଆ ଆପ୍ ପାଇଁ"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"ଅଜଣା"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
- <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
+ <string name="abandoned_search" msgid="891119232568284442">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
<string name="abandoned_promises_title" msgid="7096178467971716750">"ଏହି ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇନାହିଁ"</string>
<string name="abandoned_promise_explanation" msgid="3990027586878167529">"ଏହି ଆଇକନ୍ ପାଇଁ ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇନାହିଁ। ଏହାକୁ ଆପଣ ଆପ୍ ପାଇଁ ବାହାର କରିପାରିବେ କିମ୍ୱା ସର୍ଚ୍ଚ କରି ପାରିବେ ଏବଂ ଏହାକୁ ମାନୁଆଲ୍ ଭାବରେ ଇନଷ୍ଟଲ୍ କରିପାରିବେ।"</string>
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ଇନଷ୍ଟଲ୍ କରାଯାଉଛି, <xliff:g id="PROGRESS">%2$s</xliff:g> ସମ୍ପୂର୍ଣ୍ଣ ହୋଇଛି"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index c4ee9ae..94f5343 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -101,8 +101,8 @@
<string name="folder_tap_to_rename" msgid="4017685068016979677">"மாற்றிய பெயரைச் சேமிக்க, தட்டவும்"</string>
<string name="folder_closed" msgid="4100806530910930934">"கோப்புறை மூடப்பட்டது"</string>
<string name="folder_renamed" msgid="1794088362165669656">"கோப்புறை <xliff:g id="NAME">%1$s</xliff:g> என மறுபெயரிடப்பட்டது"</string>
- <string name="folder_name_format_exact" msgid="8626242716117004803">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> கோப்புகள்"</string>
- <string name="folder_name_format_overflow" msgid="4270108890534995199">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> அல்லது அதற்கு அதிகமான கோப்புகள்"</string>
+ <string name="folder_name_format_exact" msgid="8626242716117004803">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ஃபைல்கள்"</string>
+ <string name="folder_name_format_overflow" msgid="4270108890534995199">"கோப்புறை: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> அல்லது அதற்கு அதிகமான ஃபைல்கள்"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"வால்பேப்பர்கள்"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"வால்பேப்பர் & ஸ்டைல்"</string>
<string name="settings_button_text" msgid="8873672322605444408">"முகப்பு அமைப்புகள்"</string>
@@ -138,7 +138,7 @@
<string name="move_to_position" msgid="6750008980455459790">"நிலை <xliff:g id="NUMBER">%1$s</xliff:g>க்கு நகர்த்து"</string>
<string name="move_to_hotseat_position" msgid="6295412897075147808">"விரும்பும் நிலை <xliff:g id="NUMBER">%1$s</xliff:g>க்கு நகர்த்து"</string>
<string name="item_moved" msgid="4606538322571412879">"உருப்படி நகர்த்தப்பட்டது"</string>
- <string name="add_to_folder" msgid="9040534766770853243">"இந்தக் கோப்புறையில் சேர்க்கும்: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="add_to_folder" msgid="9040534766770853243">"இந்த ஃபோல்டரில் சேர்க்கும்: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> உள்ள கோப்புறையில் சேர்க்கும்"</string>
<string name="added_to_folder" msgid="4793259502305558003">"கோப்புறையில் உருப்படி சேர்க்கப்பட்டது"</string>
<string name="create_folder_with" msgid="4050141361160214248">"இதனுடன் கோப்புறையை உருவாக்கும்: <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 80a6f10..22aa785 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -58,7 +58,7 @@
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"విడ్జెట్ సెట్టింగ్లను మార్చడానికి ట్యాప్ చేయండి"</string>
<string name="widget_education_close_button" msgid="8676165703104836580">"అర్థమైంది"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"విడ్జెట్ సెట్టింగ్లను మార్చండి"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"అప్లికేషన్లను శోధించండి"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"అప్లికేషన్లను వెతకండి"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"అప్లికేషన్లను లోడ్ చేస్తోంది…"</string>
<string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అప్లికేషన్లేవీ కనుగొనబడలేదు"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"మరిన్ని యాప్ల కోసం వెతుకు"</string>
@@ -68,9 +68,9 @@
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"షార్ట్కట్ను తరలించడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కండి & హోల్డ్ చేయండి."</string>
<string name="out_of_space" msgid="6692471482459245734">"ఈ మొదటి స్క్రీన్లో స్థలం లేదు"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"అనువర్తనాల జాబితా"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"వ్యక్తిగత యాప్ల జాబితా"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"కార్యాలయ యాప్ల జాబితా"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"యాప్ల లిస్ట్"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"వ్యక్తిగత యాప్ల లిస్ట్"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"కార్యాలయ యాప్ల లిస్ట్"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"తీసివేయి"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్స్టాల్ చేయి"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
@@ -78,12 +78,12 @@
<string name="dismiss_prediction_label" msgid="3357562989568808658">"యాప్ను సూచించవద్దు"</string>
<string name="pin_prediction" msgid="4196423321649756498">"సూచనను పిన్ చేయండి"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"షార్ట్కట్లను ఇన్స్టాల్ చేయడం"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా సత్వరమార్గాలను జోడించడానికి యాప్ను అనుమతిస్తుంది."</string>
- <string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడం"</string>
- <string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్లో సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
- <string name="permlab_write_settings" msgid="3574213698004620587">"హోమ్ సెట్టింగ్లు మరియు సత్వరమార్గాలను వ్రాయడం"</string>
- <string name="permdesc_write_settings" msgid="5440712911516509985">"హోమ్లో సెట్టింగ్లు మరియు సత్వరమార్గాలను మార్చడానికి యాప్ను అనుమతిస్తుంది."</string>
- <string name="msg_no_phone_permission" msgid="9208659281529857371">"ఫోన్ కాల్లను చేసేందుకు <xliff:g id="APP_NAME">%1$s</xliff:g>కి అనుమతి లేదు"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా షార్ట్కట్లను జోడించడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్లు మరియు షార్ట్కట్లను చదవడం"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్లో సెట్టింగ్లు మరియు షార్ట్కట్లను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"హోమ్ సెట్టింగ్లు మరియు షార్ట్కట్లను వ్రాయడం"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"హోమ్లో సెట్టింగ్లు మరియు షార్ట్కట్లను మార్చడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="msg_no_phone_permission" msgid="9208659281529857371">"ఫోన్ కాల్స్ను చేసేందుకు <xliff:g id="APP_NAME">%1$s</xliff:g>కి అనుమతి లేదు"</string>
<string name="gadget_error_text" msgid="740356548025791839">"విడ్జెట్ను లోడ్ చేయడం సాధ్యం కాలేదు"</string>
<string name="gadget_setup_text" msgid="1745356155479272374">"సెటప్ను పూర్తి చేయడానికి ట్యాప్ చేయండి"</string>
<string name="uninstall_system_app_text" msgid="4172046090762920660">"ఇది సిస్టమ్ యాప్ మరియు దీన్ని అన్ఇన్స్టాల్ చేయడం సాధ్యపడదు."</string>
@@ -126,8 +126,8 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g>ను ఇన్స్టాల్ చేయడం, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> డౌన్లోడ్ అవుతోంది, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ఇన్స్టాల్ కావడానికి వేచి ఉంది"</string>
- <string name="widgets_list" msgid="796804551140113767">"విడ్జెట్ల జాబితా"</string>
- <string name="widgets_list_closed" msgid="6141506579418771922">"విడ్జెట్ల జాబితా మూసివేయబడింది"</string>
+ <string name="widgets_list" msgid="796804551140113767">"విడ్జెట్ల లిస్ట్"</string>
+ <string name="widgets_list_closed" msgid="6141506579418771922">"విడ్జెట్ల లిస్ట్ మూసివేయబడింది"</string>
<string name="action_add_to_workspace" msgid="8902165848117513641">"హోమ్ స్క్రీన్కు జోడించండి"</string>
<string name="action_move_here" msgid="2170188780612570250">"అంశాన్ని ఇక్కడికి తరలించు"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"అంశం హోమ్స్క్రీన్కి జోడించబడింది"</string>
@@ -141,7 +141,7 @@
<string name="add_to_folder" msgid="9040534766770853243">"ఈ ఫోల్డర్కి జోడించండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="add_to_folder_with_app" msgid="4534929978967147231">"<xliff:g id="NAME">%1$s</xliff:g> గల ఫోల్డర్కు జోడించు"</string>
<string name="added_to_folder" msgid="4793259502305558003">"అంశం ఫోల్డర్కు జోడించబడింది"</string>
- <string name="create_folder_with" msgid="4050141361160214248">"ఈ పేరుతో ఫోల్డర్ను సృష్టించండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="create_folder_with" msgid="4050141361160214248">"ఈ పేరుతో ఫోల్డర్ను క్రియేట్ చేయండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_created" msgid="6409794597405184510">"ఫోల్డర్ సృష్టించబడింది"</string>
<string name="action_move_to_workspace" msgid="1603837886334246317">"హోమ్స్క్రీన్కు తరలించు"</string>
<string name="action_resize" msgid="1802976324781771067">"పరిమాణం మార్చు"</string>
@@ -150,7 +150,7 @@
<string name="action_decrease_width" msgid="1374549771083094654">"వెడల్పును తగ్గించు"</string>
<string name="action_decrease_height" msgid="282377193880900022">"ఎత్తును తగ్గించు"</string>
<string name="widget_resized" msgid="9130327887929620">"విడ్జెట్ పరిమాణం వెడల్పు <xliff:g id="NUMBER_0">%1$s</xliff:g>కి, ఎత్తు <xliff:g id="NUMBER_1">%2$s</xliff:g>కి మార్చబడింది"</string>
- <string name="action_deep_shortcut" msgid="2864038805849372848">"సత్వరమార్గాలు"</string>
+ <string name="action_deep_shortcut" msgid="2864038805849372848">"షార్ట్కట్స్"</string>
<string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"షార్ట్కట్లు మరియు నోటిఫికేషన్లు"</string>
<string name="action_dismiss_notification" msgid="5909461085055959187">"తీసివేయి"</string>
<string name="accessibility_close" msgid="2277148124685870734">"మూసివేస్తుంది"</string>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index c2ebeff..55cedf4 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -41,6 +41,8 @@
<color name="wallpaper_popup_scrim">@android:color/system_neutral1_900</color>
<color name="folder_dot_color">@android:color/system_accent2_50</color>
+ <color name="folder_pagination_color_light">@android:color/system_accent1_600</color>
+ <color name="folder_pagination_color_dark">@android:color/system_accent2_100</color>
<color name="home_settings_header_accent">@android:color/system_accent1_600</color>
<color name="home_settings_header_collapsed">@android:color/system_neutral1_100</color>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 00cf31c..65b46cf 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -42,6 +42,7 @@
<attr name="popupNotificationDotColor" format="color" />
<attr name="folderDotColor" format="color" />
+ <attr name="folderPaginationColor" format="color" />
<attr name="folderFillColor" format="color" />
<attr name="folderIconRadius" format="float" />
<attr name="folderIconBorderColor" format="color" />
@@ -148,9 +149,16 @@
<attr name="dbFile" format="string" />
<attr name="defaultLayoutId" format="reference" />
+ <attr name="defaultSplitDisplayLayoutId" format="reference" />
<attr name="demoModeLayoutId" format="reference" />
<attr name="isScalable" format="boolean" />
<attr name="devicePaddingId" format="reference" />
+ <attr name="gridEnabled" format="integer" >
+ <!-- Enable on all devices; default value -->
+ <enum name="all_displays" value="0" />
+ <!-- Enable on single display devices only -->
+ <enum name="single_display" value="1" />
+ </attr>
</declare-styleable>
@@ -176,11 +184,20 @@
<attr name="borderSpacingDps" format="float" />
<attr name="iconImageSize" format="float" />
- <!-- landscapeIconSize defaults to iconSize, if not specified -->
+ <!-- landscapeIconSize defaults to iconImageSize, if not specified -->
<attr name="landscapeIconSize" format="float" />
+ <!-- twoPanelPortraitIconSize defaults to iconImageSize, if not specified -->
+ <attr name="twoPanelPortraitIconSize" format="float" />
+ <!-- twoPanelLandscapeIconSize defaults to landscapeIconSize, if not specified -->
+ <attr name="twoPanelLandscapeIconSize" format="float" />
+
<attr name="iconTextSize" format="float" />
<!-- landscapeIconTextSize defaults to iconTextSize, if not specified -->
<attr name="landscapeIconTextSize" format="float" />
+ <!-- twoPanelPortraitIconTextSize defaults to iconTextSize, if not specified -->
+ <attr name="twoPanelPortraitIconTextSize" format="float" />
+ <!-- twoPanelLandscapeIconTextSize defaults to landscapeIconTextSize, if not specified -->
+ <attr name="twoPanelLandscapeIconTextSize" format="float" />
<!-- If set, this display option is used to determine the default grid -->
<attr name="canBeDefault" format="boolean|integer" >
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 1b68fb6..5020127 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,15 +36,6 @@
<color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
- <color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
- <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
- <color name="fake_wallpaper_color_dark_mode">#000000</color> <!-- Black -->
- <color name="fake_wallpaper_color_light_mode">#f9f9f9</color> <!-- White -->
- <!-- Must contrast fake_wallpaper_color_dark_mode and fake_wallpaper_color_light_mode -->
- <color name="gesture_tutorial_fake_previous_task_view_color">#3C4043</color> <!-- Gray -->
- <color name="gesture_tutorial_action_button_label_color">#FF000000</color>
- <color name="gesture_tutorial_primary_color">#B7F29F</color> <!-- Light Green -->
-
<color name="popup_color_primary_light">#FFF</color>
<color name="popup_color_secondary_light">#F1F3F4</color>
<color name="popup_color_tertiary_light">#E0E0E0</color> <!-- Gray 300 -->
@@ -73,6 +64,8 @@
<color name="folder_background_dark">#464746</color>
<color name="folder_dot_color">?attr/colorPrimary</color>
+ <color name="folder_pagination_color_light">#ff006c5f</color>
+ <color name="folder_pagination_color_dark">#ffbfebe3</color>
<color name="text_color_primary_dark">#FFFFFFFF</color>
<color name="text_color_secondary_dark">#FFFFFFFF</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 04c359e..72959b2 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -34,6 +34,9 @@
<!-- View tag key used to determine if we should fade in the child views.. -->
<string name="popup_container_iterate_children" translatable="false">popup_container_iterate_children</string>
+ <!-- config used to determine if header protection is supported in AllApps -->
+ <bool name="config_header_protection_supported">false</bool>
+
<!-- Workspace -->
<!-- The duration (in ms) of the fade animation on the object outlines, used when
we are dragging objects around on the home screen. -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index da37250..5fc0480 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -110,6 +110,7 @@
<dimen name="all_apps_tip_bottom_margin">8dp</dimen>
<!-- The size of corner radius of the arrow in the arrow toast. -->
<dimen name="arrow_toast_corner_radius">2dp</dimen>
+ <dimen name="arrow_toast_elevation">2dp</dimen>
<dimen name="arrow_toast_arrow_width">10dp</dimen>
<!-- Search bar in All Apps -->
@@ -143,8 +144,7 @@
<dimen name="widget_cell_font_size">14sp</dimen>
<dimen name="widget_tabs_button_horizontal_padding">4dp</dimen>
- <dimen name="widget_tabs_horizontal_margin">32dp</dimen>
- <dimen name="widget_apps_header_pill_height">48dp</dimen>
+ <dimen name="widget_tabs_horizontal_padding">16dp</dimen>
<dimen name="widget_apps_tabs_vertical_padding">6dp</dimen>
<dimen name="recommended_widgets_table_vertical_padding">8dp</dimen>
@@ -178,8 +178,6 @@
<dimen name="widget_picker_education_tip_max_width">308dp</dimen>
<dimen name="widget_picker_education_tip_min_margin">4dp</dimen>
- <dimen name="widget_picker_view_pager_top_padding">10dp</dimen>
-
<!-- Padding applied to shortcut previews -->
<dimen name="shortcut_preview_padding_left">0dp</dimen>
<dimen name="shortcut_preview_padding_right">0dp</dimen>
@@ -243,6 +241,7 @@
<dimen name="bg_popup_padding">2dp</dimen>
<dimen name="bg_popup_item_width">216dp</dimen>
<dimen name="bg_popup_item_height">56dp</dimen>
+ <dimen name="bg_popup_item_vertical_padding">12dp</dimen>
<dimen name="pre_drag_view_scale">6dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
@@ -273,6 +272,8 @@
<!-- Notifications -->
<dimen name="bg_round_rect_radius">8dp</dimen>
+ <dimen name="notification_max_trans">8dp</dimen>
+ <dimen name="notification_space">8dp</dimen>
<dimen name="notification_padding">16dp</dimen>
<dimen name="notification_padding_top">18dp</dimen>
<dimen name="notification_header_text_size">14sp</dimen>
@@ -327,6 +328,7 @@
<dimen name="task_thumbnail_icon_drawable_size">0dp</dimen>
<dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
<dimen name="overview_task_margin">0dp</dimen>
+ <dimen name="overview_task_margin_focused">0dp</dimen>
<dimen name="overview_task_margin_grid">0dp</dimen>
<dimen name="overview_actions_margin_gesture">0dp</dimen>
<dimen name="overview_actions_top_margin_gesture_grid_portrait">0dp</dimen>
@@ -348,4 +350,5 @@
<dimen name="search_row_icon_size">48dp</dimen>
<dimen name="search_row_small_icon_size">32dp</dimen>
<dimen name="padded_rounded_button_padding">8dp</dimen>
+
</resources>
diff --git a/res/values/id.xml b/res/values/id.xml
index 0e2dff0..ebc4075 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -17,6 +17,7 @@
<resources>
<item type="id" name="apps_list_view_work" />
<item type="id" name="tag_widget_entry" />
+ <item type="id" name="view_type_widgets_space" />
<item type="id" name="view_type_widgets_list" />
<item type="id" name="view_type_widgets_header" />
<item type="id" name="view_type_widgets_search_header" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d1774e5..a6105eb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -153,6 +153,7 @@
<!-- All applications label -->
<string name="all_apps_button_label">Apps list</string>
+ <string name="all_apps_search_results">Search results</string>
<string name="all_apps_button_personal_label">Personal apps list</string>
<string name="all_apps_button_work_label">Work apps list</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b7661b9..8ad4fcd 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -50,6 +50,7 @@
<item name="workspaceStatusBarScrim">@drawable/workspace_bg</item>
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="folderDotColor">@color/folder_dot_color</item>
+ <item name="folderPaginationColor">@color/folder_pagination_color_light</item>
<item name="folderFillColor">@color/folder_background_light</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="folderTextColor">@color/workspace_text_color_dark</item>
@@ -108,6 +109,7 @@
<item name="popupShadeThird">@color/popup_shade_third_dark</item>
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="folderDotColor">@color/folder_dot_color</item>
+ <item name="folderPaginationColor">@color/folder_pagination_color_dark</item>
<item name="folderFillColor">@color/folder_background_dark</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="folderTextColor">@color/workspace_text_color_light</item>
diff --git a/res/xml/default_workspace_5x5.xml b/res/xml/default_workspace_5x5.xml
index ccdde2c..b4ac8f6 100644
--- a/res/xml/default_workspace_5x5.xml
+++ b/res/xml/default_workspace_5x5.xml
@@ -94,4 +94,5 @@
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
<favorite launcher:uri="market://details?id=com.android.launcher" />
</resolve>
+
</favorites>
diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp
deleted file mode 100644
index 9ed26ff..0000000
--- a/robolectric_tests/Android.bp
+++ /dev/null
@@ -1,63 +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.
-
-//
-// Launcher Robolectric test target.
-//
-// "robolectric_android-all-stub", not needed, we write our own stubs
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "packages_apps_Launcher3_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["packages_apps_Launcher3_license"],
-}
-
-filegroup {
- name: "launcher3-robolectric-resources",
- path: "resources",
- srcs: ["resources/*"],
-}
-
-filegroup {
- name: "launcher3-robolectric-src",
- srcs: ["src/**/*.java"],
-}
-
-android_robolectric_test {
- name: "LauncherRoboTests",
- srcs: [
- ":launcher3-robolectric-src",
- ":launcher3-test-src-common",
- ],
- java_resources: [":launcher3-robolectric-resources"],
- static_libs: [
- "truth-prebuilt",
- "androidx.test.espresso.contrib",
- "androidx.test.espresso.core",
- "androidx.test.espresso.intents",
- "androidx.test.ext.junit",
- "androidx.test.runner",
- "androidx.test.rules",
- "mockito-robolectric-prebuilt",
- "SystemUISharedLib",
- ],
- robolectric_prebuilt_version: "4.5.1",
- instrumentation_for: "Launcher3",
-
- test_options: {
- timeout: 36000,
- },
-}
diff --git a/robolectric_tests/res/values/overlayable_icons_test.xml b/robolectric_tests/res/values/overlayable_icons_test.xml
deleted file mode 100644
index 5144e52..0000000
--- a/robolectric_tests/res/values/overlayable_icons_test.xml
+++ /dev/null
@@ -1,36 +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.
--->
-<resources>
- <!-- overlayable_icons references all of the drawables in this package
- that are being overlayed by resource overlays. If you remove/rename
- any of these resources, you must also change the resource overlay icons.-->
- <array name="overlayable_icons">
- <item>@drawable/ic_corp</item>
- <item>@drawable/ic_drag_handle</item>
- <item>@drawable/ic_hourglass_top</item>
- <item>@drawable/ic_info_no_shadow</item>
- <item>@drawable/ic_install_no_shadow</item>
- <item>@drawable/ic_palette</item>
- <item>@drawable/ic_pin</item>
- <item>@drawable/ic_remove_no_shadow</item>
- <item>@drawable/ic_setting</item>
- <item>@drawable/ic_smartspace_preferences</item>
- <item>@drawable/ic_split_screen</item>
- <item>@drawable/ic_uninstall_no_shadow</item>
- <item>@drawable/ic_warning</item>
- <item>@drawable/ic_widget</item>
- </array>
-</resources>
diff --git a/robolectric_tests/resources/robolectric.properties b/robolectric_tests/resources/robolectric.properties
deleted file mode 100644
index abb6968..0000000
--- a/robolectric_tests/resources/robolectric.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-sdk=30
-
-shadows= \
- com.android.launcher3.shadows.LShadowAppPredictionManager \
- com.android.launcher3.shadows.LShadowAppWidgetManager \
- com.android.launcher3.shadows.LShadowBackupManager \
- com.android.launcher3.shadows.LShadowDisplay \
- com.android.launcher3.shadows.LShadowLauncherApps \
- com.android.launcher3.shadows.ShadowDeviceFlag \
- com.android.launcher3.shadows.ShadowLooperExecutor \
- com.android.launcher3.shadows.ShadowMainThreadInitializedObject \
- com.android.launcher3.shadows.ShadowOverrides \
- com.android.launcher3.shadows.ShadowSurfaceTransactionApplier \
-
-application=com.android.launcher3.util.LauncherTestApplication
diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
deleted file mode 100644
index e3694ae..0000000
--- a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2020 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.secondarydisplay;
-
-import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
-import static com.android.launcher3.util.LauncherUIHelper.doLayout;
-import static com.android.launcher3.util.Preconditions.assertNotNull;
-
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.allapps.AllAppsPagedView;
-import com.android.launcher3.allapps.AllAppsRecyclerView;
-import com.android.launcher3.util.LauncherLayoutBuilder;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowUserManager;
-
-/**
- * Tests for {@link SecondaryDisplayLauncher} with work profile
- */
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
-public class SDWorkModeTest {
-
- private static final int SYSTEM_USER = 0;
- private static final int FLAG_SYSTEM = 0x00000800;
- private static final int WORK_PROFILE_ID = 10;
- private static final int FLAG_PROFILE = 0x00001000;
-
- private Context mTargetContext;
- private InvariantDeviceProfile mIdp;
- private LauncherModelHelper mModelHelper;
-
- @Before
- public void setup() throws Exception {
- mModelHelper = new LauncherModelHelper();
- mTargetContext = RuntimeEnvironment.application;
- mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
- Settings.Global.putFloat(mTargetContext.getContentResolver(),
- Settings.Global.WINDOW_ANIMATION_SCALE, 0);
-
- mModelHelper.installApp(TEST_PACKAGE);
- }
-
- @Test
- public void testAllAppsList_noWorkProfile() throws Exception {
- SecondaryDisplayLauncher launcher = loadLauncher();
- launcher.showAppDrawer(true);
- doLayout(launcher);
-
- verifyRecyclerViewCount(launcher.getAppsView().getActiveRecyclerView());
- }
-
- @Test
- public void testAllAppsList_workProfile() throws Exception {
- ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class));
- sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM);
- sum.addProfile(SYSTEM_USER, WORK_PROFILE_ID, "work", FLAG_PROFILE);
-
- SecondaryDisplayLauncher launcher = loadLauncher();
- launcher.showAppDrawer(true);
- doLayout(launcher);
-
- AllAppsRecyclerView rv1 = launcher.getAppsView().getActiveRecyclerView();
- verifyRecyclerViewCount(rv1);
-
- assertNotNull(launcher.getAppsView().getWorkModeSwitch());
- assertTrue(launcher.getAppsView().getRecyclerViewContainer() instanceof AllAppsPagedView);
-
- AllAppsPagedView pagedView =
- (AllAppsPagedView) launcher.getAppsView().getRecyclerViewContainer();
- pagedView.snapToPageImmediately(1);
- doLayout(launcher);
-
- AllAppsRecyclerView rv2 = launcher.getAppsView().getActiveRecyclerView();
- verifyRecyclerViewCount(rv2);
- assertNotSame(rv1, rv2);
- }
-
- private SecondaryDisplayLauncher loadLauncher() throws Exception {
- // Install 100 apps
- for (int i = 0; i < 100; i++) {
- mModelHelper.installApp(TEST_PACKAGE + i);
- }
- mModelHelper.setupDefaultLayoutProvider(new LauncherLayoutBuilder()).loadModelSync();
- SecondaryDisplayLauncher launcher =
- Robolectric.buildActivity(SecondaryDisplayLauncher.class).setup().get();
- doLayout(launcher);
- return launcher;
- }
-
- private void verifyRecyclerViewCount(AllAppsRecyclerView rv) {
- int childCount = rv.getChildCount();
- assertTrue(childCount > 0);
- assertTrue(childCount < 100);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
deleted file mode 100644
index ae051f7..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppPredictionManager.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import static org.mockito.Mockito.mock;
-
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-/**
- * Shadow for {@link AppPredictionManager} which create mock predictors
- */
-@Implements(value = AppPredictionManager.class)
-public class LShadowAppPredictionManager {
-
- @Implementation
- public AppPredictor createAppPredictionSession(AppPredictionContext predictionContext) {
- return mock(AppPredictor.class);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
deleted file mode 100644
index 696ffd0..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java
+++ /dev/null
@@ -1,48 +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.shadows;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.os.Process;
-import android.os.UserHandle;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowAppWidgetManager;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ShadowAppWidgetManager} with missing shadow methods
- */
-@Implements(value = AppWidgetManager.class)
-public class LShadowAppWidgetManager extends ShadowAppWidgetManager {
-
- @Override
- protected List<AppWidgetProviderInfo> getInstalledProviders() {
- return getInstalledProvidersForProfile(null);
- }
-
- @Implementation
- public List<AppWidgetProviderInfo> getInstalledProvidersForProfile(UserHandle profile) {
- UserHandle user = profile == null ? Process.myUserHandle() : profile;
- return super.getInstalledProviders().stream().filter(
- info -> user.equals(info.getProfile())).collect(Collectors.toList());
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
deleted file mode 100644
index eae0101..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import android.app.backup.BackupManager;
-import android.os.UserHandle;
-import android.util.LongSparseArray;
-
-import androidx.annotation.Nullable;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowBackupManager;
-
-/**
- * Extension of {@link ShadowBackupManager} with missing shadow methods
- */
-@Implements(value = BackupManager.class)
-public class LShadowBackupManager extends ShadowBackupManager {
-
- private LongSparseArray<UserHandle> mProfileMapping = new LongSparseArray<>();
-
- public void addProfile(long userSerial, UserHandle userHandle) {
- mProfileMapping.put(userSerial, userHandle);
- }
-
- @Implementation
- @Nullable
- public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
- return mProfileMapping.get(ancestralSerialNumber);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
deleted file mode 100644
index 3813fa1..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Display;
-
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadows.ShadowDisplay;
-
-/**
- * Extension of {@link ShadowDisplay} with missing shadow methods
- */
-@Implements(value = Display.class)
-public class LShadowDisplay extends ShadowDisplay {
-
- private final Rect mInsets = new Rect();
-
- @RealObject Display realObject;
-
- /**
- * Sets the insets for the display
- */
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- }
-
- @Override
- protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
- directlyOn(realObject, Display.class).getCurrentSizeRange(outSmallestSize, outLargestSize);
- outSmallestSize.x -= mInsets.left + mInsets.right;
- outLargestSize.x -= mInsets.left + mInsets.right;
-
- outSmallestSize.y -= mInsets.top + mInsets.bottom;
- outLargestSize.y -= mInsets.top + mInsets.bottom;
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
deleted file mode 100644
index 6a6f0fb..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ /dev/null
@@ -1,136 +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.shadows;
-
-import static org.robolectric.util.ReflectionHelpers.ClassParameter;
-import static org.robolectric.util.ReflectionHelpers.callConstructor;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-import android.util.ArraySet;
-
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
-
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.ShadowLauncherApps;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ShadowLauncherApps} with missing shadow methods
- */
-@Implements(value = LauncherApps.class)
-public class LShadowLauncherApps extends ShadowLauncherApps {
-
- public final ArraySet<PackageUserKey> disabledApps = new ArraySet<>();
- public final ArraySet<ComponentKey> disabledActivities = new ArraySet<>();
-
- @Implementation
- @Override
- protected List<ShortcutInfo> getShortcuts(LauncherApps.ShortcutQuery query, UserHandle user) {
- try {
- return super.getShortcuts(query, user);
- } catch (UnsupportedOperationException e) {
- return Collections.emptyList();
- }
- }
-
- @Implementation
- protected boolean isPackageEnabled(String packageName, UserHandle user) {
- return !disabledApps.contains(new PackageUserKey(packageName, user));
- }
-
- @Implementation
- protected boolean isActivityEnabled(ComponentName component, UserHandle user) {
- return !disabledActivities.contains(new ComponentKey(component, user));
- }
-
- @Implementation
- protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
- ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
- .resolveActivity(intent, 0);
- return ri == null ? null : getLauncherActivityInfo(ri.activityInfo, user);
- }
-
- public LauncherActivityInfo getLauncherActivityInfo(
- ActivityInfo activityInfo, UserHandle user) {
- return callConstructor(LauncherActivityInfo.class,
- ClassParameter.from(Context.class, RuntimeEnvironment.application),
- ClassParameter.from(ActivityInfo.class, activityInfo),
- ClassParameter.from(UserHandle.class, user));
- }
-
- @Implementation
- public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
- throws PackageManager.NameNotFoundException {
- return RuntimeEnvironment.application.getPackageManager()
- .getApplicationInfo(packageName, flags);
- }
-
- @Implementation
- public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
- Intent intent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_LAUNCHER)
- .setPackage(packageName);
- return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
- .stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
- .collect(Collectors.toList());
- }
-
- @Implementation
- public boolean hasShortcutHostPermission() {
- return true;
- }
-
- @Implementation
- public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
- return RuntimeEnvironment.application.getPackageManager().getPackageInstaller()
- .getAllSessions();
- }
-
- @Implementation
- public void registerPackageInstallerSessionCallback(
- Executor executor, PackageInstaller.SessionCallback callback) {
- }
-
- @Override
- protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
- UserHandle user) {
- Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
- return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
- .stream()
- .map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
- .collect(Collectors.toList());
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
deleted file mode 100644
index b58e4b7..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowDeviceFlag.java
+++ /dev/null
@@ -1,62 +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.shadows;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.uioverrides.DeviceFlag;
-import com.android.launcher3.util.LooperExecutor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.shadow.api.Shadow;
-
-/**
- * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
- */
-@Implements(value = DeviceFlag.class, isInAndroidSdk = false)
-public class ShadowDeviceFlag {
-
- @RealObject private DeviceFlag mRealObject;
- @Nullable private Boolean mValue;
-
- /**
- * Mock change listener as it uses internal system classes not available to robolectric
- */
- @Implementation
- protected void addChangeListener(Context context, Runnable r) { }
-
- @Implementation
- protected static boolean getDeviceValue(String key, boolean defaultValue) {
- return defaultValue;
- }
-
- @Implementation
- public boolean get() {
- if (mValue != null) {
- return mValue;
- }
- return Shadow.directlyOn(mRealObject, DeviceFlag.class, "get");
- }
-
- public void setValue(boolean value) {
- mValue = new Boolean(value);
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
deleted file mode 100644
index 57eda7e..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.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.launcher3.shadows;
-
-import static com.android.launcher3.util.Executors.createAndStartNewLooper;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-import static org.robolectric.util.ReflectionHelpers.setField;
-
-import android.os.Handler;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.util.LooperExecutor;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-/**
- * Shadow for {@link LooperExecutor} to provide reset functionality for static executors.
- */
-@Implements(value = LooperExecutor.class, isInAndroidSdk = false)
-public class ShadowLooperExecutor {
-
- @RealObject private LooperExecutor mRealExecutor;
-
- private Handler mOverriddenHandler;
-
- @Implementation
- protected Handler getHandler() {
- if (mOverriddenHandler != null) {
- return mOverriddenHandler;
- }
- Handler handler = directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
- Thread thread = handler.getLooper().getThread();
- if (!thread.isAlive()) {
- // Robolectric destroys all loopers at the end of every test. Since Launcher maintains
- // some static threads, they need to be reinitialized in case they were destroyed.
- setField(mRealExecutor, "mHandler",
- new Handler(createAndStartNewLooper(thread.getName())));
- }
- return directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
- }
-
- public void setHandler(@Nullable Handler handler) {
- mOverriddenHandler = handler;
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
deleted file mode 100644
index 6e2ccf8..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java
+++ /dev/null
@@ -1,61 +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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
-import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Set;
-import java.util.WeakHashMap;
-
-/**
- * Shadow for {@link MainThreadInitializedObject} to provide reset functionality for static sObjects
- */
-@Implements(value = MainThreadInitializedObject.class, isInAndroidSdk = false)
-public class ShadowMainThreadInitializedObject {
-
- // Keep reference to all created MainThreadInitializedObject so they can be cleared after test
- private static Set<MainThreadInitializedObject> sObjects =
- Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
-
- @RealObject private MainThreadInitializedObject mRealObject;
-
- @Implementation
- protected void __constructor__(ObjectProvider provider) {
- invokeConstructor(MainThreadInitializedObject.class, mRealObject,
- from(ObjectProvider.class, provider));
- sObjects.add(mRealObject);
- }
-
- /**
- * Resets all the initialized sObjects to be null
- */
- public static void resetInitializedObjects() {
- for (MainThreadInitializedObject object : new ArrayList<>(sObjects)) {
- object.initializeForTesting(null);
- }
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
deleted file mode 100644
index 131f691..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import android.content.Context;
-
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Shadow for {@link Overrides} to provide custom overrides for test
- */
-@Implements(value = Overrides.class, isInAndroidSdk = false)
-public class ShadowOverrides {
-
- private static Map<Class, ObjectProvider> sProviderMap = new HashMap<>();
-
- @Implementation
- public static <T extends ResourceBasedOverride> T getObject(
- Class<T> clazz, Context context, int resId) {
- ObjectProvider<T> provider = sProviderMap.get(clazz);
- if (provider != null) {
- return provider.get(context);
- }
- return Shadow.directlyOn(Overrides.class, "getObject",
- ClassParameter.from(Class.class, clazz),
- ClassParameter.from(Context.class, context),
- ClassParameter.from(int.class, resId));
- }
-
- public static <T> void setProvider(Class<T> clazz, ObjectProvider<T> provider) {
- sProviderMap.put(clazz, provider);
- }
-
- public static void clearProvider() {
- sProviderMap.clear();
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
deleted file mode 100644
index a9f2f27..0000000
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowSurfaceTransactionApplier.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 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.shadows;
-
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
-import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
-
-import android.view.View;
-
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-/**
- * Shadow for SurfaceTransactionApplier to override default functionality
- */
-@Implements(className = "com.android.quickstep.util.SurfaceTransactionApplier",
- isInAndroidSdk = false)
-public class ShadowSurfaceTransactionApplier {
-
- @RealObject
- private Object mRealObject;
-
- @Implementation
- protected void __constructor__(View view) {
- invokeConstructor(mRealObject.getClass(), mRealObject, from(View.class, null));
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java
deleted file mode 100644
index 17d0ac1..0000000
--- a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.testing;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.BaseDragLayer;
-
-/** An empty activity for {@link android.app.Fragment}s, {@link android.view.View}s testing. */
-public class TestActivity extends BaseActivity implements ActivityContext {
-
- private DeviceProfile mDeviceProfile;
-
- @Override
- public BaseDragLayer getDragLayer() {
- return new BaseDragLayer(this, /* attrs= */ null, /* alphaChannelCount= */ 1) {
- @Override
- public void recreateControllers() {
- // Do nothing.
- }
- };
- }
-
- @Override
- public DeviceProfile getDeviceProfile() {
- return mDeviceProfile;
- }
-
- public void setDeviceProfile(DeviceProfile deviceProfile) {
- mDeviceProfile = deviceProfile;
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
deleted file mode 100644
index ea75548..0000000
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui;
-
-import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
-import static com.android.launcher3.util.LauncherUIHelper.buildAndBindLauncher;
-import static com.android.launcher3.util.LauncherUIHelper.doLayout;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerProperties;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderPagedView;
-import com.android.launcher3.util.LauncherLayoutBuilder;
-import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder;
-import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.widget.picker.WidgetsFullSheet;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
-import org.robolectric.shadows.ShadowLooper;
-
-/**
- * Tests scroll behavior at various Launcher UI components
- */
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
-@org.junit.Ignore
-public class LauncherUIScrollTest {
-
- private Context mTargetContext;
- private InvariantDeviceProfile mIdp;
- private LauncherModelHelper mModelHelper;
-
- private LauncherLayoutBuilder mLayoutBuilder;
-
- @Before
- public void setup() throws Exception {
- mModelHelper = new LauncherModelHelper();
- mTargetContext = RuntimeEnvironment.application;
- mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
-
- Settings.Global.putFloat(mTargetContext.getContentResolver(),
- Settings.Global.WINDOW_ANIMATION_SCALE, 0);
-
- mModelHelper.installApp(TEST_PACKAGE);
- // LayoutBuilder with 3 workspace pages
- mLayoutBuilder = new LauncherLayoutBuilder()
- .atWorkspace(0, mIdp.numRows - 1, 0).putApp(TEST_PACKAGE, TEST_PACKAGE)
- .atWorkspace(0, mIdp.numRows - 1, 1).putApp(TEST_PACKAGE, TEST_PACKAGE)
- .atWorkspace(0, mIdp.numRows - 1, 2).putApp(TEST_PACKAGE, TEST_PACKAGE);
- }
-
- @Test
- public void testWorkspacePagesBound() throws Exception {
- // Verify that the workspace if bound synchronously
- Launcher launcher = loadLauncher();
- assertEquals(3, launcher.getWorkspace().getPageCount());
- assertEquals(0, launcher.getWorkspace().getCurrentPage());
-
- launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
- assertNotEquals("Workspace was not scrolled",
- 0, launcher.getWorkspace().getNextPage());
- }
-
- @Test
- public void testAllAppsScroll() throws Exception {
- // Install 100 apps
- for (int i = 0; i < 100; i++) {
- mModelHelper.installApp(TEST_PACKAGE + i);
- }
-
- // Bind and open all-apps
- Launcher launcher = loadLauncher();
- launcher.getStateManager().goToState(LauncherState.ALL_APPS, false);
- doLayout(launcher);
-
- int currentScroll = launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
- launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
- int newScroll = launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
-
- assertNotEquals("All Apps was not scrolled", currentScroll, newScroll);
- assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage());
- }
-
- @Test
- public void testWidgetsListScroll() throws Exception {
- // Install 100 widgets
- for (int i = 0; i < 100; i++) {
- mModelHelper.installCustomShortcut(TEST_PACKAGE + i, "shortcutProvider");
- }
-
- // Bind and open widgets
- Launcher launcher = loadLauncher();
- WidgetsFullSheet widgets = WidgetsFullSheet.show(launcher, false);
- doLayout(launcher);
-
- int currentScroll = widgets.getRecyclerView().getCurrentScrollY();
- launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
- int newScroll = widgets.getRecyclerView().getCurrentScrollY();
- assertNotEquals("Widgets was not scrolled", currentScroll, newScroll);
- assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage());
- }
-
- @Test
- public void testFolderPageScroll() throws Exception {
- // Add a folder with multiple icons
- FolderBuilder fb = mLayoutBuilder.atWorkspace(mIdp.numColumns / 2, mIdp.numRows / 2, 0)
- .putFolder(0);
- for (int i = 0; i < 100; i++) {
- fb.addApp(TEST_PACKAGE, TEST_PACKAGE);
- }
-
- // Bind and open folder
- Launcher launcher = loadLauncher();
- doLayout(launcher);
- launcher.getWorkspace().getFirstMatch((i, v) -> v instanceof FolderIcon).performClick();
- ShadowLooper.idleMainLooper();
- doLayout(launcher);
- FolderPagedView folderPages = Folder.getOpen(launcher).getContent();
-
- assertEquals(0, folderPages.getNextPage());
- launcher.dispatchGenericMotionEvent(createScrollEvent(-1));
- assertNotEquals("Folder page was not scrolled", 0, folderPages.getNextPage());
- assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage());
- }
-
- private Launcher loadLauncher() throws Exception {
- mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder).loadModelSync();
- return buildAndBindLauncher();
- }
-
- private static MotionEvent createScrollEvent(int scroll) {
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE
- .get(RuntimeEnvironment.application).supportedProfiles.get(0);
-
- final PointerProperties[] pointerProperties = new PointerProperties[1];
- pointerProperties[0] = new PointerProperties();
- pointerProperties[0].id = 0;
- final MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
- coords[0] = new MotionEvent.PointerCoords();
- coords[0].setAxisValue(MotionEvent.AXIS_VSCROLL, scroll);
- coords[0].x = dp.widthPx / 2;
- coords[0].y = dp.heightPx / 2;
-
- final long time = SystemClock.uptimeMillis();
- return MotionEvent.obtain(time, time, MotionEvent.ACTION_SCROLL, 1,
- pointerProperties, coords, 0, 0, 1.0f, 1.0f, 0, 0,
- InputDevice.SOURCE_CLASS_POINTER, 0);
- }
-
-}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java b/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
deleted file mode 100644
index efac150..0000000
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherTestApplication.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2020 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.mockito.Mockito.mock;
-
-import android.app.Application;
-
-import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
-import com.android.launcher3.shadows.ShadowOverrides;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-
-import org.robolectric.TestLifecycleApplication;
-import org.robolectric.shadows.ShadowLog;
-
-import java.lang.reflect.Method;
-
-public class LauncherTestApplication extends Application implements TestLifecycleApplication {
-
- @Override
- public void beforeTest(Method method) {
- ShadowLog.stream = System.out;
-
- // Disable plugins
- PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class));
- }
-
- @Override
- public void prepareTest(Object test) { }
-
- @Override
- public void afterTest(Method method) {
- ShadowLog.stream = null;
- ShadowMainThreadInitializedObject.resetInitializedObjects();
- ShadowOverrides.clearProvider();
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
deleted file mode 100644
index caad40e..0000000
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
-import static com.android.launcher3.Utilities.createHomeIntent;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.graphics.Point;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.launcher3.Launcher;
-
-import org.robolectric.Robolectric;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.android.controller.ActivityController;
-import org.robolectric.shadows.ShadowLooper;
-import org.robolectric.util.ReflectionHelpers;
-
-import java.util.List;
-
-/**
- * Utility class to help manage Launcher UI and related objects for test.
- */
-public class LauncherUIHelper {
-
- /**
- * Returns the class name for the Launcher activity as defined in the manifest
- */
- public static String getLauncherClassName() {
- Context context = RuntimeEnvironment.application;
- Intent homeIntent = createHomeIntent().setPackage(context.getPackageName());
-
- List<ResolveInfo> launchers = context.getPackageManager()
- .queryIntentActivities(homeIntent, 0);
- if (launchers.size() != 1) {
- return null;
- }
- return launchers.get(0).activityInfo.name;
- }
-
- /**
- * Returns an activity controller for Launcher activity defined in the manifest
- */
- public static <T extends Launcher> ActivityController<T> buildLauncher() {
- try {
- Class<T> tClass = (Class<T>) Class.forName(getLauncherClassName());
- return Robolectric.buildActivity(tClass);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Creates and binds a Launcher activity defined in the manifest.
- * Note that the model must be bound before calling this
- */
- public static <T extends Launcher> T buildAndBindLauncher() {
- ActivityController<T> controller = buildLauncher();
-
- T launcher = controller.setup().get();
- doLayout(launcher);
- ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor");
- if (executor != null) {
- executor.markCompleted();
- }
- return launcher;
- }
-
- /**
- * Performs a measure and layout pass for the given activity
- */
- public static void doLayout(Activity activity) {
- Point size = new Point();
- RuntimeEnvironment.application.getSystemService(WindowManager.class)
- .getDefaultDisplay().getSize(size);
- View view = activity.getWindow().getDecorView();
- view.measure(makeMeasureSpec(size.x, EXACTLY), makeMeasureSpec(size.y, EXACTLY));
- view.layout(0, 0, size.x, size.y);
- ShadowLooper.idleMainLooper();
- }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java b/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java
deleted file mode 100644
index 1090d1e..0000000
--- a/robolectric_tests/src/com/android/launcher3/widget/CachingWidgetPreviewLoaderTest.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.os.UserHandle;
-import android.util.Size;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.testing.TestActivity;
-import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class CachingWidgetPreviewLoaderTest {
- private final Size SIZE_10_10 = new Size(10, 10);
- private final Size SIZE_20_20 = new Size(20, 20);
- private static final String TEST_PACKAGE = "com.example.test";
- private final ComponentName TEST_PROVIDER =
- new ComponentName(TEST_PACKAGE, ".WidgetProvider");
- private final ComponentName TEST_PROVIDER2 =
- new ComponentName(TEST_PACKAGE, ".WidgetProvider2");
- private final Bitmap BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
- private final Bitmap BITMAP2 = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888);
-
-
- @Mock private CancellationSignal mCancellationSignal;
- @Mock private WidgetPreviewLoader mDelegate;
- @Mock private IconCache mIconCache;
- @Mock private DeviceProfile mDeviceProfile;
- @Mock private LauncherAppWidgetProviderInfo mProviderInfo;
- @Mock private LauncherAppWidgetProviderInfo mProviderInfo2;
- @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback;
- @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback2;
- @Captor private ArgumentCaptor<WidgetPreviewLoadedCallback> mCallbackCaptor;
-
- private TestActivity mTestActivity;
- private CachingWidgetPreviewLoader mLoader;
- private WidgetItem mWidgetItem;
- private WidgetItem mWidgetItem2;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mLoader = new CachingWidgetPreviewLoader(mDelegate);
-
- mTestActivity = Robolectric.buildActivity(TestActivity.class).setup().get();
- mTestActivity.setDeviceProfile(mDeviceProfile);
-
- when(mDelegate.loadPreview(any(), any(), any(), any())).thenReturn(mCancellationSignal);
-
- mProviderInfo.provider = TEST_PROVIDER;
- when(mProviderInfo.getProfile()).thenReturn(new UserHandle(0));
-
- mProviderInfo2.provider = TEST_PROVIDER2;
- when(mProviderInfo2.getProfile()).thenReturn(new UserHandle(0));
-
- InvariantDeviceProfile testProfile = new InvariantDeviceProfile();
- testProfile.numRows = 5;
- testProfile.numColumns = 5;
-
- mWidgetItem = new WidgetItem(mProviderInfo, testProfile, mIconCache);
- mWidgetItem2 = new WidgetItem(mProviderInfo2, testProfile, mIconCache);
- }
-
- @Test
- public void getPreview_notInCache_shouldReturnNull() {
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
- }
-
- @Test
- public void getPreview_notInCache_shouldNotCallDelegate() {
- mLoader.getPreview(mWidgetItem, SIZE_10_10);
-
- verifyZeroInteractions(mDelegate);
- }
-
- @Test
- public void getPreview_inCache_shouldReturnCachedBitmap() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- }
-
- @Test
- public void getPreview_otherSizeInCache_shouldReturnNull() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isNull();
- }
-
- @Test
- public void getPreview_otherItemInCache_shouldReturnNull() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.getPreview(mWidgetItem2, SIZE_10_10)).isNull();
- }
-
- @Test
- public void getPreview_shouldStoreMultipleSizesPerItem() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP2);
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isEqualTo(BITMAP2);
- }
-
- @Test
- public void loadPreview_notInCache_shouldStartLoading() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- verify(mDelegate).loadPreview(eq(mTestActivity), eq(mWidgetItem), eq(SIZE_10_10), any());
- verifyZeroInteractions(mPreviewLoadedCallback);
- }
-
- @Test
- public void loadPreview_thenLoaded_shouldCallBack() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- loaderCallback.onPreviewLoaded(BITMAP);
-
- verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
- }
-
- @Test
- public void loadPreview_thenCancelled_shouldCancelDelegateRequest() {
- CancellationSignal cancellationSignal =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- cancellationSignal.cancel();
-
- verify(mCancellationSignal).cancel();
- verifyZeroInteractions(mPreviewLoadedCallback);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
- }
-
- @Test
- public void loadPreview_thenCancelled_otherCallListening_shouldNotCancelDelegateRequest() {
- CancellationSignal cancellationSignal1 =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- cancellationSignal1.cancel();
-
- verifyZeroInteractions(mCancellationSignal);
- }
-
- @Test
- public void loadPreview_thenCancelled_otherCallListening_loaded_shouldCallBackToNonCancelled() {
- CancellationSignal cancellationSignal1 =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- cancellationSignal1.cancel();
- loaderCallback.onPreviewLoaded(BITMAP);
-
- verifyZeroInteractions(mPreviewLoadedCallback);
- verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- }
-
- @Test
- public void loadPreview_thenCancelled_bothCallsCancelled_shouldCancelDelegateRequest() {
- CancellationSignal cancellationSignal1 =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- CancellationSignal cancellationSignal2 =
- mLoader.loadPreview(
- mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- cancellationSignal1.cancel();
- cancellationSignal2.cancel();
-
- verify(mCancellationSignal).cancel();
- verifyZeroInteractions(mPreviewLoadedCallback);
- verifyZeroInteractions(mPreviewLoadedCallback2);
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
- }
-
- @Test
- public void loadPreview_multipleCallbacks_shouldOnlyCallDelegateOnce() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- verify(mDelegate).loadPreview(any(), any(), any(), any());
- }
-
- @Test
- public void loadPreview_multipleCallbacks_shouldForwardResultToEachCallback() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
-
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- loaderCallback.onPreviewLoaded(BITMAP);
-
- verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
- verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
- }
-
- @Test
- public void loadPreview_inCache_shouldCallBackImmediately() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- reset(mDelegate);
-
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
- verifyZeroInteractions(mDelegate);
- }
-
- @Test
- public void loadPreview_thenLoaded_thenCancelled_shouldNotRemovePreviewFromCache() {
- CancellationSignal cancellationSignal =
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
- loaderCallback.onPreviewLoaded(BITMAP);
-
- cancellationSignal.cancel();
-
- assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
- }
-
- @Test
- public void isPreviewLoaded_notLoaded_shouldReturnFalse() {
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void isPreviewLoaded_otherSizeLoaded_shouldReturnFalse() {
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void isPreviewLoaded_otherItemLoaded_shouldReturnFalse() {
- loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void isPreviewLoaded_loaded_shouldReturnTrue() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isTrue();
- }
-
- @Test
- public void clearPreviews_notInCache_shouldBeNoOp() {
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearPreviews_inCache_shouldRemovePreview() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearPreviews_inCache_multipleSizes_shouldRemoveAllSizes() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
- }
-
- @Test
- public void clearPreviews_inCache_otherItems_shouldOnlyRemoveSpecifiedItems() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isTrue();
- }
-
- @Test
- public void clearPreviews_inCache_otherItems_shouldRemoveAllSpecifiedItems() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);
-
- mLoader.clearPreviews(Arrays.asList(mWidgetItem, mWidgetItem2));
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearPreviews_loading_shouldCancelLoad() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- mLoader.clearPreviews(Collections.singletonList(mWidgetItem));
-
- verify(mCancellationSignal).cancel();
- }
-
- @Test
- public void clearAll_cacheEmpty_shouldBeNoOp() {
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearAll_inCache_shouldRemovePreview() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
-
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- }
-
- @Test
- public void clearAll_inCache_multipleSizes_shouldRemoveAllSizes() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
-
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
- }
-
- @Test
- public void clearAll_inCache_multipleItems_shouldRemoveAll() {
- loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
- loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
- loadPreviewIntoCache(mWidgetItem2, SIZE_20_20, BITMAP);
-
- mLoader.clearAll();
-
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
- assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_20_20)).isFalse();
- }
-
- @Test
- public void clearAll_loading_shouldCancelLoad() {
- mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
-
- mLoader.clearAll();
-
- verify(mCancellationSignal).cancel();
- }
-
- private void loadPreviewIntoCache(WidgetItem widgetItem, Size size, Bitmap bitmap) {
- reset(mDelegate);
- mLoader.loadPreview(mTestActivity, widgetItem, size, ignored -> {});
- verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
- WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
-
- loaderCallback.onPreviewLoaded(bitmap);
- }
-}
diff --git a/robolectric_tests/unstaged/SettingsCacheTest.java b/robolectric_tests/unstaged/SettingsCacheTest.java
deleted file mode 100644
index fbf4c63..0000000
--- a/robolectric_tests/unstaged/SettingsCacheTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.net.Uri;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class SettingsCacheTest {
-
- public static final Uri KEY_SYSTEM_URI_TEST1 = Uri.parse("content://settings/system/test1");
- public static final Uri KEY_SYSTEM_URI_TEST2 = Uri.parse("content://settings/system/test2");;
-
- private SettingsCache.OnChangeListener mChangeListener;
- private SettingsCache mSettingsCache;
-
- @Before
- public void setup() {
- mChangeListener = mock(SettingsCache.OnChangeListener.class);
- Context targetContext = RuntimeEnvironment.application;
- mSettingsCache = SettingsCache.INSTANCE.get(targetContext);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST1, mChangeListener);
- }
-
- @Test
- public void listenerCalledOnChange() {
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- verify(mChangeListener, times(1)).onSettingsChanged(true);
- }
-
- @Test
- public void getValueRespectsDefaultValue() {
- // Case of key not found
- boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertFalse(val);
- }
-
- @Test
- public void getValueHitsCache() {
- mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
- boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertTrue(val);
- }
-
- @Test
- public void getValueUpdatedCache() {
- // First ensure there's nothing in cache
- boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertFalse(val);
-
- mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
- val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
- assertTrue(val);
- }
-
- @Test
- public void multipleListenersSingleKey() {
- SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST1, secondListener);
-
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- verify(mChangeListener, times(1)).onSettingsChanged(true);
- verify(secondListener, times(1)).onSettingsChanged(true);
- }
-
- @Test
- public void singleListenerMultipleKeys() {
- SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST2, secondListener);
-
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
- verify(mChangeListener, times(1)).onSettingsChanged(true);
- verify(secondListener, times(1)).onSettingsChanged(true);
- }
-
- @Test
- public void sameListenerMultipleKeys() {
- SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
- mSettingsCache.register(KEY_SYSTEM_URI_TEST2, mChangeListener);
-
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
- mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
- verify(mChangeListener, times(2)).onSettingsChanged(true);
- verify(secondListener, times(0)).onSettingsChanged(true);
- }
-}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 4979b40..e080537 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -64,7 +64,8 @@
TYPE_OPTIONS_POPUP,
TYPE_ICON_SURFACE,
TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP,
- TYPE_WIDGETS_EDUCATION_DIALOG
+ TYPE_WIDGETS_EDUCATION_DIALOG,
+ TYPE_TASKBAR_EDUCATION_DIALOG
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
@@ -87,18 +88,20 @@
public static final int TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP = 1 << 14;
public static final int TYPE_WIDGETS_EDUCATION_DIALOG = 1 << 15;
+ public static final int TYPE_TASKBAR_EDUCATION_DIALOG = 1 << 16;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
| TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
- | TYPE_WIDGETS_EDUCATION_DIALOG;
+ | TYPE_WIDGETS_EDUCATION_DIALOG | TYPE_TASKBAR_EDUCATION_DIALOG;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
- | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG;
+ | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG
+ | TYPE_TASKBAR_EDUCATION_DIALOG;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 9778b61..ec96c6d 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -145,6 +145,7 @@
/**
* Returns {@link StatsLogManager} for user event logging.
*/
+ @Override
public StatsLogManager getStatsLogManager() {
if (mStatsLogManager == null) {
mStatsLogManager = StatsLogManager.newInstance(this);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 02fe7d9..54920e1 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -90,6 +90,10 @@
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+ private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+
+ private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
+
private float mScaleForReorderBounce = 1f;
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
@@ -254,9 +258,27 @@
@UiThread
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
+ applyFromWorkspaceItem(info, /* animate = */ false, /* staggerIndex = */ 0);
+ }
+
+ @UiThread
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
applyFromWorkspaceItem(info, false);
}
+ /**
+ * Returns whether the newInfo differs from the current getTag().
+ */
+ public boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
+ WorkspaceItemInfo oldInfo = getTag() instanceof WorkspaceItemInfo
+ ? (WorkspaceItemInfo) getTag()
+ : null;
+ boolean changedIcons = oldInfo != null && oldInfo.getTargetComponent() != null
+ && newInfo.getTargetComponent() != null
+ && !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
+ return changedIcons && isShown();
+ }
+
@Override
public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
if (delegate instanceof LauncherAccessibilityDelegate) {
@@ -389,6 +411,10 @@
* Returns true if the touch down at the provided position be ignored
*/
protected boolean shouldIgnoreTouchDown(float x, float y) {
+ if (mDisplay == DISPLAY_TASKBAR) {
+ // Allow touching within padding on taskbar, given icon sizes are smaller.
+ return false;
+ }
return y < getPaddingTop()
|| x < getPaddingLeft()
|| y > getHeight() - getPaddingBottom()
@@ -800,8 +826,11 @@
}
private void updateTranslation() {
- super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
- super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+ + mTranslationForMoveFromCenterAnimation.x
+ + mTranslationXForTaskbarAlignmentAnimation);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+ + mTranslationForMoveFromCenterAnimation.y);
}
public void setReorderBounceOffset(float x, float y) {
@@ -834,6 +863,29 @@
return mScaleForReorderBounce;
}
+ /**
+ * Sets translation values for move from center animation
+ */
+ public void setTranslationForMoveFromCenterAnimation(float x, float y) {
+ mTranslationForMoveFromCenterAnimation.set(x, y);
+ updateTranslation();
+ }
+
+ /**
+ * Sets translationX for taskbar to launcher alignment animation
+ */
+ public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
+ mTranslationXForTaskbarAlignmentAnimation = translationX;
+ updateTranslation();
+ }
+
+ /**
+ * Returns translationX value for taskbar to launcher alignment animation
+ */
+ public float getTranslationXForTaskbarAlignmentAnimation() {
+ return mTranslationXForTaskbarAlignmentAnimation;
+ }
+
public View getView() {
return this;
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7acec1f..b7d0481 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -17,7 +17,6 @@
package com.android.launcher3;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.launcher3.ResourceUtils.pxFromDp;
import static com.android.launcher3.Utilities.dpiFromPx;
@@ -33,11 +32,8 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
import android.util.DisplayMetrics;
import android.view.Surface;
-import android.view.WindowInsets;
-import android.view.WindowManager;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
@@ -183,6 +179,7 @@
// Overview
public final boolean overviewShowAsGrid;
public int overviewTaskMarginPx;
+ public int overviewTaskMarginGridPx;
public int overviewTaskIconSizePx;
public int overviewTaskIconDrawableSizePx;
public int overviewTaskIconDrawableSizeGridPx;
@@ -214,6 +211,8 @@
// Taskbar
public boolean isTaskbarPresent;
+ // Whether Taskbar will inset the bottom of apps by taskbarSize.
+ public boolean isTaskbarPresentInApps;
public int taskbarSize;
// How much of the bottom inset is due to Taskbar rather than other system elements.
public int nonOverlappingTaskbarInset;
@@ -221,6 +220,7 @@
// DragController
public int flingToDeleteThresholdVelocity;
+ /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
boolean useTwoPanels) {
@@ -248,7 +248,8 @@
// Tablet UI does not support emulated landscape.
isTablet = allowRotation && info.isTablet(windowBounds);
isPhone = !isTablet;
- isTwoPanels = isTablet && useTwoPanels;
+ isTwoPanels = isTablet && useTwoPanels
+ && (isLandscape || FeatureFlags.ENABLE_TWO_PANEL_HOME_IN_PORTRAIT.get());
aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
@@ -267,13 +268,7 @@
// Taskbar will be added later, but provides bottom insets that we should subtract
// from availableHeightPx.
taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size);
- WindowInsets windowInsets =
- context.createWindowContext(
- context.getSystemService(DisplayManager.class).getDisplay(mInfo.id),
- TYPE_APPLICATION, null)
- .getSystemService(WindowManager.class)
- .getCurrentWindowMetrics().getWindowInsets();
- nonOverlappingTaskbarInset = taskbarSize - windowInsets.getSystemWindowInsetBottom();
+ nonOverlappingTaskbarInset = taskbarSize - windowBounds.insets.bottom;
if (nonOverlappingTaskbarInset > 0) {
nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
}
@@ -361,8 +356,9 @@
overviewShowAsGrid = isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
overviewTaskMarginPx = overviewShowAsGrid
- ? res.getDimensionPixelSize(R.dimen.overview_task_margin_grid)
+ ? res.getDimensionPixelSize(R.dimen.overview_task_margin_focused)
: res.getDimensionPixelSize(R.dimen.overview_task_margin);
+ overviewTaskMarginGridPx = res.getDimensionPixelSize(R.dimen.overview_task_margin_grid);
overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
overviewTaskIconDrawableSizePx =
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
@@ -612,12 +608,30 @@
iconScale = Math.min(1f, scale);
cellScaleToFit = scale;
-
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
- float invIconSizeDp = isLandscape ? inv.landscapeIconSize : inv.iconSize;
+ float invIconSizeDp;
+ float invIconTextSizeSp;
+
+ if (isTwoPanels) {
+ if (isLandscape) {
+ invIconSizeDp = inv.twoPanelLandscapeIconSize;
+ invIconTextSizeSp = inv.twoPanelLandscapeIconTextSize;
+ } else {
+ invIconSizeDp = inv.twoPanelPortraitIconSize;
+ invIconTextSizeSp = inv.twoPanelPortraitIconTextSize;
+ }
+ } else {
+ if (isLandscape) {
+ invIconSizeDp = inv.landscapeIconSize;
+ invIconTextSizeSp = inv.landscapeIconTextSize;
+ } else {
+ invIconSizeDp = inv.iconSize;
+ invIconTextSizeSp = inv.iconTextSize;
+ }
+ }
+
iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, iconScale));
- float invIconTextSizeSp = isLandscape ? inv.landscapeIconTextSize : inv.iconTextSize;
iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
@@ -772,7 +786,7 @@
Point padding = getTotalWorkspacePadding();
// availableWidthPx is the screen width of the device. In 2 panels mode, each panel should
// only have half of the screen width. In addition, there is only cellLayoutPadding in the
- // left side of the left panel and the right side of the right panel. There is no
+ // left side of the left most panel and the right most side of the right panel. There is no
// cellLayoutPadding in the middle.
int screenWidthPx = isTwoPanels
? availableWidthPx / 2 - padding.x - cellLayoutPaddingLeftRightPx
@@ -1071,6 +1085,7 @@
writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
+ writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize));
writer.println(prefix + pxToDpStr("nonOverlappingTaskbarInset",
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 88f6c49..eb42a65 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@@ -32,10 +33,13 @@
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.testing.TestProtocol;
/*
* The top bar containing various drop targets: Delete/App Info/Uninstall.
@@ -212,6 +216,9 @@
}
public void animateToVisibility(boolean isVisible) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "8");
+ }
if (mVisible != isVisible) {
mVisible = isVisible;
@@ -238,6 +245,9 @@
*/
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "7");
+ }
animateToVisibility(true);
}
@@ -261,4 +271,12 @@
public ButtonDropTarget[] getDropTargets() {
return mDropTargets;
}
+
+ @Override
+ protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (TestProtocol.sDebugTracing && visibility == VISIBLE) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "9");
+ }
+ }
}
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index a4e1af6..21bc479 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -99,8 +99,18 @@
}
}
- // inherited class can override to change the appearance of the edit text.
- public void show() {}
+ /**
+ * Sets whether EditText background should be visible
+ * @param maxAlpha defines the maximum alpha the background should animates to
+ */
+ public void setBackgroundVisibility(boolean visible, float maxAlpha) {}
+
+ /**
+ * Returns whether a visible background is set on EditText
+ */
+ public boolean getBackgroundVisibility() {
+ return getBackground() != null;
+ }
public void showKeyboard() {
mShowImeAfterFirstLayout = !showSoftInput();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 10b3f98..244cb59 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -35,6 +35,7 @@
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
@@ -56,6 +57,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -68,6 +70,8 @@
private static final int DEFAULT_TRUE = -1;
private static final int DEFAULT_SPLIT_DISPLAY = 2;
+ private static final int GRID_ENABLED_ALL_DISPLAYS = 0;
+ private static final int GRID_ENABLED_SINGLE_DISPLAY = 1;
private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
@@ -94,12 +98,17 @@
public int numFolderColumns;
public float iconSize;
public float landscapeIconSize;
+ public float twoPanelPortraitIconSize;
+ public float twoPanelLandscapeIconSize;
public float landscapeIconTextSize;
+ public float twoPanelPortraitIconTextSize;
+ public float twoPanelLandscapeIconTextSize;
public int iconBitmapSize;
public int fillResIconDpi;
public float iconTextSize;
public float allAppsIconSize;
public float allAppsIconTextSize;
+ public boolean isSplitDisplay;
public float minCellHeight;
public float minCellWidth;
@@ -150,34 +159,6 @@
@VisibleForTesting
public InvariantDeviceProfile() {}
- private InvariantDeviceProfile(InvariantDeviceProfile p) {
- numRows = p.numRows;
- numColumns = p.numColumns;
- numFolderRows = p.numFolderRows;
- numFolderColumns = p.numFolderColumns;
- iconSize = p.iconSize;
- landscapeIconSize = p.landscapeIconSize;
- iconBitmapSize = p.iconBitmapSize;
- iconTextSize = p.iconTextSize;
- landscapeIconTextSize = p.landscapeIconTextSize;
- numShownHotseatIcons = p.numShownHotseatIcons;
- numDatabaseHotseatIcons = p.numDatabaseHotseatIcons;
- numAllAppsColumns = p.numAllAppsColumns;
- numDatabaseAllAppsColumns = p.numDatabaseAllAppsColumns;
- isScalable = p.isScalable;
- devicePaddingId = p.devicePaddingId;
- minCellHeight = p.minCellHeight;
- minCellWidth = p.minCellWidth;
- borderSpacing = p.borderSpacing;
- dbFile = p.dbFile;
- allAppsIconSize = p.allAppsIconSize;
- allAppsIconTextSize = p.allAppsIconTextSize;
- defaultLayoutId = p.defaultLayoutId;
- demoModeLayoutId = p.demoModeLayoutId;
- mExtraAttrs = p.mExtraAttrs;
- devicePaddings = p.devicePaddings;
- }
-
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
String gridName = getCurrentGridName(context);
@@ -224,13 +205,13 @@
DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
.add(myDisplayOption);
- result.iconSize = defaultDisplayOption.iconSize;
- result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
- if (defaultDisplayOption.allAppsIconSize < myDisplayOption.allAppsIconSize) {
- result.allAppsIconSize = defaultDisplayOption.allAppsIconSize;
- } else {
- result.allAppsIconSize = myDisplayOption.allAppsIconSize;
+ result.iconSizes[DisplayOption.INDEX_DEFAULT] =
+ defaultDisplayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
+ for (int i = 1; i < DisplayOption.COUNT_TOTAL; i++) {
+ result.iconSizes[i] = Math.min(
+ defaultDisplayOption.iconSizes[i], myDisplayOption.iconSizes[i]);
}
+
result.minCellHeight = defaultDisplayOption.minCellHeight;
result.minCellWidth = defaultDisplayOption.minCellWidth;
result.borderSpacing = defaultDisplayOption.borderSpacing;
@@ -272,16 +253,25 @@
numFolderColumns = closestProfile.numFolderColumns;
isScalable = closestProfile.isScalable;
devicePaddingId = closestProfile.devicePaddingId;
+ this.isSplitDisplay = isSplitDisplay;
mExtraAttrs = closestProfile.extraAttrs;
- iconSize = displayOption.iconSize;
- landscapeIconSize = displayOption.landscapeIconSize;
+ iconSize = displayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
+ landscapeIconSize = displayOption.iconSizes[DisplayOption.INDEX_LANDSCAPE];
+ twoPanelPortraitIconSize = displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
+ twoPanelLandscapeIconSize =
+ displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
- iconTextSize = displayOption.iconTextSize;
- landscapeIconTextSize = displayOption.landscapeIconTextSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
+ iconTextSize = displayOption.textSizes[DisplayOption.INDEX_DEFAULT];
+ landscapeIconTextSize = displayOption.textSizes[DisplayOption.INDEX_LANDSCAPE];
+ twoPanelPortraitIconTextSize =
+ displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
+ twoPanelLandscapeIconTextSize =
+ displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
+
minCellHeight = displayOption.minCellHeight;
minCellWidth = displayOption.minCellWidth;
borderSpacing = displayOption.borderSpacing;
@@ -295,8 +285,8 @@
? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
if (Utilities.isGridOptionsEnabled(context)) {
- allAppsIconSize = displayOption.allAppsIconSize;
- allAppsIconTextSize = displayOption.allAppsIconTextSize;
+ allAppsIconSize = displayOption.iconSizes[DisplayOption.INDEX_ALL_APPS];
+ allAppsIconTextSize = displayOption.textSizes[DisplayOption.INDEX_ALL_APPS];
} else {
allAppsIconSize = iconSize;
allAppsIconTextSize = iconTextSize;
@@ -357,13 +347,22 @@
MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
}
+ private Object[] toModelState() {
+ return new Object[] {
+ numColumns, numRows, numDatabaseHotseatIcons, iconBitmapSize, fillResIconDpi,
+ numDatabaseAllAppsColumns, dbFile};
+ }
+
private void onConfigChanged(Context context) {
+ Object[] oldState = toModelState();
+
// Re-init grid
String gridName = getCurrentGridName(context);
initGrid(context, gridName);
+ boolean modelPropsChanged = !Arrays.equals(oldState, toModelState());
for (OnIDPChangeListener listener : mChangeListeners) {
- listener.onIdpChanged(this);
+ listener.onIdpChanged(modelPropsChanged);
}
}
@@ -378,16 +377,19 @@
if ((type == XmlPullParser.START_TAG)
&& GridOption.TAG_NAME.equals(parser.getName())) {
- GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser));
- final int displayDepth = parser.getDepth();
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > displayDepth)
- && type != XmlPullParser.END_DOCUMENT) {
- if ((type == XmlPullParser.START_TAG) && "display-option".equals(
- parser.getName())) {
- profiles.add(new DisplayOption(gridOption, context,
- Xml.asAttributeSet(parser),
- isSplitDisplay ? DEFAULT_SPLIT_DISPLAY : DEFAULT_TRUE));
+ GridOption gridOption =
+ new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
+ if (gridOption.isEnabled) {
+ final int displayDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG
+ || parser.getDepth() > displayDepth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ if ((type == XmlPullParser.START_TAG) && "display-option".equals(
+ parser.getName())) {
+ profiles.add(new DisplayOption(gridOption, context,
+ Xml.asAttributeSet(parser),
+ isSplitDisplay ? DEFAULT_SPLIT_DISPLAY : DEFAULT_TRUE));
+ }
}
}
}
@@ -399,7 +401,7 @@
ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
if (!TextUtils.isEmpty(gridName)) {
for (DisplayOption option : profiles) {
- if (gridName.equals(option.grid.name)) {
+ if (gridName.equals(option.grid.name) && option.grid.isEnabled) {
filteredProfiles.add(option);
}
}
@@ -418,6 +420,32 @@
return filteredProfiles;
}
+ /**
+ * @return all the grid options that can be shown on the device
+ */
+ public List<GridOption> parseAllGridOptions(Context context) {
+ List<GridOption> result = new ArrayList<>();
+ try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
+ final int depth = parser.getDepth();
+ int type;
+ while (((type = parser.next()) != XmlPullParser.END_TAG
+ || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+ if ((type == XmlPullParser.START_TAG)
+ && GridOption.TAG_NAME.equals(parser.getName())) {
+ GridOption option =
+ new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
+ if (option.isEnabled) {
+ result.add(option);
+ }
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Error parsing device profile", e);
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
private int getLauncherIconDensity(int requiredSize) {
// Densities typically defined by an app.
int[] densityBuckets = new int[] {
@@ -487,37 +515,51 @@
Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
dist(width, height, b.minWidthDps, b.minHeightDps)));
- GridOption closestOption = points.get(0).grid;
+ DisplayOption closestPoint = points.get(0);
+ GridOption closestOption = closestPoint.grid;
float weights = 0;
- DisplayOption p = points.get(0);
- if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
- return p;
+ if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) {
+ return closestPoint;
}
DisplayOption out = new DisplayOption(closestOption);
for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
- p = points.get(i);
+ DisplayOption p = points.get(i);
float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
weights += w;
out.add(new DisplayOption().add(p).multiply(w));
}
- return out.multiply(1.0f / weights);
+ out.multiply(1.0f / weights);
+
+ // Since the bitmaps are persisted, ensure that the default bitmap size is same as
+ // predefined size to avoid cache invalidation
+ out.iconSizes[DisplayOption.INDEX_DEFAULT] =
+ closestPoint.iconSizes[DisplayOption.INDEX_DEFAULT];
+ for (int i = DisplayOption.INDEX_DEFAULT + 1; i < DisplayOption.COUNT_TOTAL; i++) {
+ out.iconSizes[i] = Math.min(out.iconSizes[i],
+ out.iconSizes[DisplayOption.INDEX_DEFAULT]);
+ }
+
+ return out;
}
public DeviceProfile getDeviceProfile(Context context) {
Resources res = context.getResources();
Configuration config = context.getResources().getConfiguration();
- float availableWidth = config.screenWidthDp * res.getDisplayMetrics().density;
- float availableHeight = config.screenHeightDp * res.getDisplayMetrics().density;
+ float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
+ float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
+ return getBestMatch(screenWidth, screenHeight);
+ }
+ public DeviceProfile getBestMatch(float screenWidth, float screenHeight) {
DeviceProfile bestMatch = supportedProfiles.get(0);
float minDiff = Float.MAX_VALUE;
for (DeviceProfile profile : supportedProfiles) {
- float diff = Math.abs(profile.availableWidthPx - availableWidth)
- + Math.abs(profile.availableHeightPx - availableHeight);
+ float diff = Math.abs(profile.widthPx - screenWidth)
+ + Math.abs(profile.heightPx - screenHeight);
if (diff < minDiff) {
minDiff = diff;
bestMatch = profile;
@@ -568,7 +610,7 @@
/**
* Called when the device provide changes
*/
- void onIdpChanged(InvariantDeviceProfile profile);
+ void onIdpChanged(boolean modelPropertiesChanged);
}
@@ -579,6 +621,7 @@
public final String name;
public final int numRows;
public final int numColumns;
+ public final boolean isEnabled;
private final int numFolderRows;
private final int numFolderColumns;
@@ -598,7 +641,7 @@
private final SparseArray<TypedValue> extraAttrs;
- public GridOption(Context context, AttributeSet attrs) {
+ public GridOption(Context context, AttributeSet attrs, boolean isSplitDisplay) {
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.GridDisplayOption);
name = a.getString(R.styleable.GridDisplayOption_name);
@@ -606,8 +649,10 @@
numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
- defaultLayoutId = a.getResourceId(
- R.styleable.GridDisplayOption_defaultLayoutId, 0);
+ defaultLayoutId = a.getResourceId(isSplitDisplay && a.hasValue(
+ R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId)
+ ? R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId
+ : R.styleable.GridDisplayOption_defaultLayoutId, 0);
demoModeLayoutId = a.getResourceId(
R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
@@ -631,6 +676,12 @@
devicePaddingId = a.getResourceId(
R.styleable.GridDisplayOption_devicePaddingId, 0);
+ final int enabledInt =
+ a.getInteger(R.styleable.GridDisplayOption_gridEnabled,
+ GRID_ENABLED_ALL_DISPLAYS);
+ isEnabled = enabledInt == GRID_ENABLED_ALL_DISPLAYS
+ || enabledInt == GRID_ENABLED_SINGLE_DISPLAY && !isSplitDisplay;
+
a.recycle();
extraAttrs = Themes.createValueMap(context, attrs,
IntArray.wrap(R.styleable.GridDisplayOption));
@@ -640,6 +691,14 @@
@VisibleForTesting
static final class DisplayOption {
+ static final int INDEX_DEFAULT = 0;
+ static final int INDEX_LANDSCAPE = 1;
+ static final int INDEX_ALL_APPS = 2;
+ static final int INDEX_TWO_PANEL_PORTRAIT = 3;
+ static final int INDEX_TWO_PANEL_LANDSCAPE = 4;
+
+ static final int COUNT_TOTAL = 5;
+
public final GridOption grid;
private final float minWidthDps;
@@ -650,12 +709,8 @@
private float minCellWidth;
private float borderSpacing;
- private float iconSize;
- private float iconTextSize;
- private float landscapeIconSize;
- private float landscapeIconTextSize;
- private float allAppsIconSize;
- private float allAppsIconTextSize;
+ private final float[] iconSizes = new float[COUNT_TOTAL];
+ private final float[] textSizes = new float[COUNT_TOTAL];
DisplayOption(GridOption grid, Context context, AttributeSet attrs, int defaultFlagValue) {
this.grid = grid;
@@ -673,17 +728,36 @@
minCellWidth = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthDps, 0);
borderSpacing = a.getFloat(R.styleable.ProfileDisplayOption_borderSpacingDps, 0);
- iconSize = a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
- landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
- iconSize);
- iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
- landscapeIconTextSize = a.getFloat(
- R.styleable.ProfileDisplayOption_landscapeIconTextSize, iconTextSize);
+ iconSizes[INDEX_DEFAULT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
+ iconSizes[INDEX_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_ALL_APPS] =
+ a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_TWO_PANEL_PORTRAIT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconSize,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_TWO_PANEL_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconSize,
+ iconSizes[INDEX_LANDSCAPE]);
- allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
- iconSize);
- allAppsIconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
- iconTextSize);
+ textSizes[INDEX_DEFAULT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+ textSizes[INDEX_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconTextSize,
+ textSizes[INDEX_DEFAULT]);
+ textSizes[INDEX_ALL_APPS] =
+ a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
+ textSizes[INDEX_DEFAULT]);
+ textSizes[INDEX_TWO_PANEL_PORTRAIT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconTextSize,
+ textSizes[INDEX_DEFAULT]);
+ textSizes[INDEX_TWO_PANEL_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconTextSize,
+ textSizes[INDEX_LANDSCAPE]);
+
a.recycle();
}
@@ -702,12 +776,10 @@
}
private DisplayOption multiply(float w) {
- iconSize *= w;
- landscapeIconSize *= w;
- allAppsIconSize *= w;
- iconTextSize *= w;
- landscapeIconTextSize *= w;
- allAppsIconTextSize *= w;
+ for (int i = 0; i < COUNT_TOTAL; i++) {
+ iconSizes[i] *= w;
+ textSizes[i] *= w;
+ }
minCellHeight *= w;
minCellWidth *= w;
borderSpacing *= w;
@@ -715,12 +787,10 @@
}
private DisplayOption add(DisplayOption p) {
- iconSize += p.iconSize;
- landscapeIconSize += p.landscapeIconSize;
- allAppsIconSize += p.allAppsIconSize;
- iconTextSize += p.iconTextSize;
- landscapeIconTextSize += p.landscapeIconTextSize;
- allAppsIconTextSize += p.allAppsIconTextSize;
+ for (int i = 0; i < COUNT_TOTAL; i++) {
+ iconSizes[i] += p.iconSizes[i];
+ textSizes[i] += p.textSizes[i];
+ }
minCellHeight += p.minCellHeight;
minCellWidth += p.minCellWidth;
borderSpacing += p.borderSpacing;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 696c308..ed9f044 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -36,7 +36,6 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -556,10 +555,18 @@
}
@Override
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+ super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+ // Always update device profile when multi window mode changed.
+ initDeviceProfile(mDeviceProfile.inv);
+ dispatchDeviceProfileChanged();
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
int diff = newConfig.diff(mOldConfig);
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
- onIdpChanged(mDeviceProfile.inv);
+ onIdpChanged(false);
}
mOldConfig.setTo(newConfig);
@@ -567,8 +574,8 @@
}
@Override
- public void onIdpChanged(InvariantDeviceProfile idp) {
- initDeviceProfile(idp);
+ public void onIdpChanged(boolean modelPropertiesChanged) {
+ initDeviceProfile(mDeviceProfile.inv);
dispatchDeviceProfileChanged();
reapplyUi();
mDragLayer.recreateControllers();
@@ -872,11 +879,11 @@
if (dropLayout == null) {
// it's possible that the add screen was removed because it was
// empty and a re-bind occurred
- mWorkspace.addExtraEmptyScreen();
- return mWorkspace.commitExtraEmptyScreen();
- } else {
- return screenId;
+ mWorkspace.addExtraEmptyScreens();
+ IntSet emptyPagesAdded = mWorkspace.commitExtraEmptyScreens();
+ return emptyPagesAdded.isEmpty() ? -1 : emptyPagesAdded.getArray().get(0);
}
+ return screenId;
}
@Thunk
@@ -1196,7 +1203,7 @@
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
// default state, otherwise we will update to the wrong offsets in RTL
mWorkspace.lockWallpaperToDefaultPage();
- mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
+ mWorkspace.bindAndInitFirstWorkspaceScreen();
mDragController.addDragListener(mWorkspace);
// Get the search/delete/uninstall bar
@@ -1298,7 +1305,7 @@
}
if (!foundCellSpan) {
- mWorkspace.onNoCellFound(layout);
+ mWorkspace.onNoCellFound(layout, info, /* logInstanceId= */ null);
return;
}
@@ -2099,34 +2106,27 @@
? mWorkspace.getCurrentPageScreenIds() : mPagesToBindSynchronously;
IntArray actualIds = new IntArray();
- if (mDeviceProfile.isTwoPanels) {
- actualIds.add(LEFT_PANEL_ID);
- } else {
- visibleIds.remove(LEFT_PANEL_ID);
- }
IntSet result = new IntSet();
if (visibleIds.isEmpty()) {
return result;
}
for (int id : orderedScreenIds.toArray()) {
- if (id != LEFT_PANEL_ID) {
- actualIds.add(id);
- }
+ actualIds.add(id);
}
int firstId = visibleIds.getArray().get(0);
+ int pairId = mWorkspace.getPagePair(firstId);
+ // Double check that actual screenIds contains the visibleId, as empty screens are hidden
+ // in single panel.
if (actualIds.contains(firstId)) {
result.add(firstId);
-
- if (mDeviceProfile.isTwoPanels) {
- int index = actualIds.indexOf(firstId);
- int nextIndex = ((int) (index / 2)) * 2;
- if (nextIndex == index) {
- nextIndex++;
- }
- if (nextIndex < actualIds.size()) {
- result.add(actualIds.get(nextIndex));
- }
+ if (mDeviceProfile.isTwoPanels && actualIds.contains(pairId)) {
+ result.add(pairId);
}
+ } else if (LauncherAppState.getIDP(this).supportedProfiles.stream().anyMatch(
+ deviceProfile -> deviceProfile.isTwoPanels) && actualIds.contains(pairId)) {
+ // Add the right panel if left panel is hidden when switching display, due to empty
+ // pages being hidden in single panel.
+ result.add(pairId);
}
return result;
}
@@ -2176,19 +2176,14 @@
@Override
public void bindScreens(IntArray orderedScreenIds) {
- // Make sure the first screen is at the start if there's no widget panel,
- // or on the second place if the first is the widget panel
- boolean isLeftPanelShown =
- mWorkspace.mWorkspaceScreens.containsKey(LEFT_PANEL_ID);
- int firstScreenPosition = isLeftPanelShown && orderedScreenIds.size() > 1 ? 1 : 0;
-
+ int firstScreenPosition = 0;
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
- mWorkspace.addExtraEmptyScreen();
+ mWorkspace.addExtraEmptyScreens();
}
bindAddScreens(orderedScreenIds);
@@ -2199,6 +2194,14 @@
}
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.getPagePair(screenId)));
+ orderedScreenIds = screenIds.getArray();
+ }
+
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
int screenId = orderedScreenIds.get(i);
@@ -2206,12 +2209,6 @@
// No need to bind the first screen, as its always bound.
continue;
}
-
- if (screenId == LEFT_PANEL_ID) {
- // No need to bind the left panel, as its always bound.
- continue;
- }
-
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
}
@@ -2232,6 +2229,9 @@
ArrayList<ItemInfo> addAnimated) {
// Add the new screens
if (newScreens != null) {
+ // newScreens can contain an empty right panel that is already bound, but not known
+ // by BgDataModel.
+ newScreens.removeAllValues(mWorkspace.mScreenOrder);
bindAddScreens(newScreens);
}
@@ -2287,11 +2287,6 @@
continue;
}
- // Skip if the item is on the left widget panel but the panel is not shown
- if (item.screenId == LEFT_PANEL_ID && !getDeviceProfile().isTwoPanels) {
- continue;
- }
-
final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 3d6be69..86217d7 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -48,10 +48,9 @@
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.custom.CustomWidgetManager;
-public class LauncherAppState {
+public class LauncherAppState implements SafeCloseable {
public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
private static final String KEY_ICON_STATE = "pref_icon_shape_path";
@@ -64,7 +63,6 @@
private final LauncherModel mModel;
private final IconProvider mIconProvider;
private final IconCache mIconCache;
- private final DatabaseWidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
private final RunnableList mOnTerminateCallback = new RunnableList();
@@ -85,7 +83,11 @@
Log.v(Launcher.TAG, "LauncherAppState initiated");
Preconditions.assertUIThread();
- mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher());
+ mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
+ if (modelPropertiesChanged) {
+ refreshAndReloadLauncher();
+ }
+ });
mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
@@ -139,7 +141,6 @@
mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context));
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
- mWidgetCache = new DatabaseWidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
mOnTerminateCallback.add(mIconCache::close);
}
@@ -155,14 +156,14 @@
LauncherIcons.clearPool();
mIconCache.updateIconParams(
mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
- mWidgetCache.refresh();
mModel.forceReload();
}
/**
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
- public void onTerminate() {
+ @Override
+ public void close() {
mModel.destroy();
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
@@ -181,10 +182,6 @@
return mModel;
}
- public DatabaseWidgetPreviewLoader getWidgetCache() {
- return mWidgetCache;
- }
-
public InvariantDeviceProfile getInvariantDeviceProfile() {
return mInvariantDeviceProfile;
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 6966abf..9ebec0a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -72,6 +72,7 @@
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -95,9 +96,10 @@
// our monitoring of the package manager provides all updates and we never
// need to do a requery. This is only ever touched from the loader thread.
private boolean mModelLoaded;
+ private boolean mModelDestroyed = false;
public boolean isModelLoaded() {
synchronized (mLock) {
- return mModelLoaded && mLoaderTask == null;
+ return mModelLoaded && mLoaderTask == null && !mModelDestroyed;
}
}
@@ -244,6 +246,7 @@
* Called when the model is destroyed
*/
public void destroy() {
+ mModelDestroyed = true;
MODEL_EXECUTOR.execute(mModelDelegate::destroy);
}
@@ -383,7 +386,13 @@
loaderResults.bindWidgets();
return true;
} else {
- startLoaderForResults(loaderResults);
+ stopLoader();
+ mLoaderTask = new LoaderTask(
+ mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
+
+ // Always post the loader task, instead of running directly
+ // (even on same thread) so that we exit any nested synchronized blocks
+ MODEL_EXECUTOR.post(mLoaderTask);
}
}
}
@@ -406,25 +415,17 @@
}
}
- public void startLoaderForResults(LoaderResults results) {
+ /**
+ * Loads the model if not loaded
+ * @param callback called with the data model upon successful load or null on model thread.
+ */
+ public void loadAsync(Consumer<BgDataModel> callback) {
synchronized (mLock) {
- stopLoader();
- mLoaderTask = new LoaderTask(
- mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results);
-
- // Always post the loader task, instead of running directly (even on same thread) so
- // that we exit any nested synchronized blocks
- MODEL_EXECUTOR.post(mLoaderTask);
- }
- }
-
- public void startLoaderForResultsIfNotLoaded(LoaderResults results) {
- synchronized (mLock) {
- if (!isModelLoaded()) {
- Log.d(TAG, "Workspace not loaded, loading now");
- startLoaderForResults(results);
+ if (!mModelLoaded && !mIsLoaderTaskRunning) {
+ startLoader();
}
}
+ MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null));
}
@Override
@@ -558,6 +559,9 @@
}
public void enqueueModelUpdateTask(ModelUpdateTask task) {
+ if (mModelDestroyed) {
+ return;
+ }
task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
MODEL_EXECUTOR.execute(task);
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 49f20c6..df09f29 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -97,7 +97,7 @@
* Represents the schema of the database. Changes in scheme need not be backwards compatible.
* When increasing the scheme version, ensure that downgrade_schema.json is updated
*/
- public static final int SCHEMA_VERSION = 29;
+ public static final int SCHEMA_VERSION = 30;
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
@@ -864,6 +864,11 @@
}
}
case 29: {
+ // Remove widget panel related leftover workspace items
+ db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
+ Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
+ }
+ case 30: {
// DB Upgraded successfully
return;
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index eb3f94c..108091c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -108,6 +108,8 @@
// relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a
// page.
protected int mCurrentPageScrollDiff;
+ // The current page the PagedView is scrolling over on it's way to the destination page.
+ protected int mCurrentScrollOverPage;
@ViewDebug.ExportedProperty(category = "launcher")
protected int mNextPage = INVALID_PAGE;
@@ -180,6 +182,7 @@
mScroller = new OverScroller(context, SCROLL);
mCurrentPage = 0;
+ mCurrentScrollOverPage = 0;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
@@ -289,7 +292,7 @@
newPage = Utilities.boundToRange(newPage, 0, getPageCount() - 1);
if (getPanelCount() > 1) {
- // Always return left panel as new page
+ // Always return left most panel as new page
newPage = getLeftmostVisiblePageForIndex(newPage);
}
return newPage;
@@ -437,6 +440,7 @@
}
int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage;
mCurrentPage = validateNewPage(currentPage);
+ mCurrentScrollOverPage = mCurrentPage;
updateCurrentPageScroll();
notifyPageSwitchListener(prevPage);
invalidate();
@@ -557,9 +561,11 @@
if (newPos < mMinScroll && oldPos >= mMinScroll) {
mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
+ onEdgeAbsorbingScroll();
} else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
+ onEdgeAbsorbingScroll();
}
}
@@ -577,6 +583,7 @@
sendScrollAccessibilityEvent();
int prevPage = mCurrentPage;
mCurrentPage = validateNewPage(mNextPage);
+ mCurrentScrollOverPage = mCurrentPage;
mNextPage = INVALID_PAGE;
notifyPageSwitchListener(prevPage);
@@ -766,7 +773,8 @@
childStart += primaryDimension + getChildGap();
// This makes sure that the space is added after the page, not after each panel
- if (i % panelCount == panelCount - 1) {
+ int lastPanel = mIsRtl ? 0 : panelCount - 1;
+ if (i % panelCount == lastPanel) {
childStart += mPageSpacing;
}
}
@@ -774,7 +782,7 @@
if (panelCount > 1) {
for (int i = 0; i < childCount; i++) {
- // In case we have multiple panels, always use left panel's page scroll for all
+ // In case we have multiple panels, always use left most panel's page scroll for all
// panels on the screen.
int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
if (outPageScrolls[i] != adjustedScroll) {
@@ -837,6 +845,7 @@
public void onViewRemoved(View child) {
super.onViewRemoved(child);
mCurrentPage = validateNewPage(mCurrentPage);
+ mCurrentScrollOverPage = mCurrentPage;
dispatchPageCountChanged();
}
@@ -1046,7 +1055,7 @@
/**
* If being flinged and user touches the screen, initiate drag; otherwise don't.
*/
- private void updateIsBeingDraggedOnTouchDown(MotionEvent ev) {
+ protected void updateIsBeingDraggedOnTouchDown(MotionEvent ev) {
// mScroller.isFinished should be false when being flinged.
final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
final boolean finishedScrolling = (mScroller.isFinished() || xDist < mPageSlop / 3);
@@ -1408,6 +1417,20 @@
protected void onNotSnappingToPageInFreeScroll() { }
+ /**
+ * Called when the view edges absorb part of the scroll. Subclasses can override this
+ * to provide custom behavior during animation.
+ */
+ protected void onEdgeAbsorbingScroll() {
+ }
+
+ /**
+ * Called when the current page closest to the center of the screen changes as part of the
+ * scroll. Subclasses can override this to provide custom behavior during scroll.
+ */
+ protected void onScrollOverPageChanged() {
+ }
+
protected boolean shouldFlingForVelocity(int velocity) {
float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
return Math.abs(velocity) > threshold;
@@ -1553,7 +1576,7 @@
return getDisplacementFromScreenCenter(childIndex, screenCenter);
}
- private int getScreenCenter(int primaryScroll) {
+ protected int getScreenCenter(int primaryScroll) {
float primaryScale = mOrientationHandler.getPrimaryScale(this);
float primaryPivot = mOrientationHandler.getPrimaryValue(getPivotX(), getPivotY());
int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
@@ -1692,6 +1715,15 @@
}
@Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ int newDestinationPage = getDestinationPage();
+ if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) {
+ mCurrentScrollOverPage = newDestinationPage;
+ onScrollOverPageChanged();
+ }
+ }
+
+ @Override
public CharSequence getAccessibilityClassName() {
// Some accessibility services have special logic for ScrollView. Since we provide same
// accessibility info as ScrollView, inform the service to handle use the same way.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 83ca08d..54b2c96 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import static androidx.annotation.VisibleForTesting.PROTECTED;
-
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -65,7 +63,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
-import androidx.annotation.VisibleForTesting;
+import androidx.annotation.Nullable;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
@@ -86,6 +84,7 @@
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.data.AppInfo;
@@ -124,7 +123,9 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -223,6 +224,9 @@
// Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
private float mXDown;
private float mYDown;
+ private View mQsb;
+ private boolean mIsEventOverQsb;
+
final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
@@ -334,14 +338,17 @@
int paddingBottom = grid.cellLayoutBottomPaddingPx;
int panelCount = getPanelCount();
- for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
+ int rightPanelModulus = mIsRtl ? 0 : panelCount - 1;
+ int leftPanelModulus = mIsRtl ? panelCount - 1 : 0;
+ int numberOfScreens = mScreenOrder.size();
+ for (int i = 0; i < numberOfScreens; i++) {
int paddingLeft = paddingLeftRight;
int paddingRight = paddingLeftRight;
if (panelCount > 1) {
- if (i % panelCount == 0) { // left side panel
+ if (i % panelCount == leftPanelModulus) {
paddingLeft = paddingLeftRight;
paddingRight = 0;
- } else if (i % panelCount == panelCount - 1) { // right side panel
+ } else if (i % panelCount == rightPanelModulus) {
paddingLeft = 0;
paddingRight = paddingLeftRight;
} else { // middle panel
@@ -349,7 +356,9 @@
paddingRight = 0;
}
}
- mWorkspaceScreens.valueAt(i).setPadding(paddingLeft, 0, paddingRight, paddingBottom);
+ // SparseArrayMap doesn't keep the order
+ mWorkspaceScreens.get(mScreenOrder.get(i))
+ .setPadding(paddingLeft, 0, paddingRight, paddingBottom);
}
}
@@ -462,7 +471,6 @@
}
@Override
- @VisibleForTesting(otherwise = PROTECTED)
public int getPanelCount() {
return isTwoPanelEnabled() ? 2 : super.getPanelCount();
}
@@ -546,23 +554,19 @@
/**
* Initializes and binds the first page
- * @param qsb an existing qsb to recycle or null.
*/
- public void bindAndInitFirstWorkspaceScreen(View qsb) {
+ public void bindAndInitFirstWorkspaceScreen() {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
- if (isTwoPanelEnabled()) {
- insertNewWorkspaceScreen(Workspace.LEFT_PANEL_ID, getChildCount());
- }
// Add the first page
CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
// Always add a QSB on the first screen.
- if (qsb == null) {
+ if (mQsb == null) {
// In transposed layout, we add the QSB in the Grid. As workspace does not touch the
// edges, we do not need a full width QSB.
- qsb = LayoutInflater.from(getContext())
+ mQsb = LayoutInflater.from(getContext())
.inflate(R.layout.search_container_workspace, firstPage, false);
}
@@ -571,8 +575,9 @@
CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(),
cellVSpan);
lp.canReorder = false;
- if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) {
+ if (!firstPage.addViewToCellLayout(mQsb, 0, R.id.search_container_workspace, lp, true)) {
Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
+ mQsb = null;
}
}
@@ -582,9 +587,8 @@
disableLayoutTransitions();
// Recycle the QSB widget
- View qsb = findViewById(R.id.search_container_workspace);
- if (qsb != null) {
- ((ViewGroup) qsb.getParent()).removeView(qsb);
+ if (mQsb != null) {
+ ((ViewGroup) mQsb.getParent()).removeView(mQsb);
}
// Remove the pages and clear the screen models
@@ -597,7 +601,7 @@
mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
// Ensure that the first page is always present
- bindAndInitFirstWorkspaceScreen(qsb);
+ bindAndInitFirstWorkspaceScreen();
// Re-enable the layout transitions
enableLayoutTransitions();
@@ -643,18 +647,36 @@
boolean childOnFinalScreen = false;
if (mDragSourceInternal != null) {
+ int dragSourceChildCount = mDragSourceInternal.getChildCount();
+
+ // If the icon was dragged from Hotseat, there is no page pair
+ if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) {
+ int pagePairScreenId = getPagePair(dragObject.dragInfo.screenId);
+ CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
+ if (pagePair == null) {
+ // TODO: after http://b/198820019 is fixed, remove this
+ throw new IllegalStateException("Page pair is null, "
+ + "dragScreenId: " + dragObject.dragInfo.screenId
+ + ", pagePairScreenId: " + pagePairScreenId
+ + ", mScreenOrder: " + mScreenOrder.toConcatString()
+ );
+ }
+ dragSourceChildCount += pagePair.getShortcutsAndWidgets().getChildCount();
+ }
+
// When the drag view content is a LauncherAppWidgetHostView, we should increment the
// drag source child count by 1 because the widget in drag has been detached from its
// original parent, ShortcutAndWidgetContainer, and reattached to the DragView.
- int dragSourceChildCount =
- dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView
- ? mDragSourceInternal.getChildCount() + 1
- : mDragSourceInternal.getChildCount();
+ if (dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView) {
+ dragSourceChildCount++;
+ }
+
if (dragSourceChildCount == 1) {
lastChildOnScreen = true;
}
CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
- if (indexOfChild(cl) == getChildCount() - 1) {
+ if (getLeftmostVisiblePageForIndex(indexOfChild(cl))
+ == getLeftmostVisiblePageForIndex(getPageCount() - 1)) {
childOnFinalScreen = true;
}
}
@@ -663,39 +685,83 @@
if (lastChildOnScreen && childOnFinalScreen) {
return;
}
- if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
- insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
+
+ forEachExtraEmptyPageId(extraEmptyPageId -> {
+ if (!mWorkspaceScreens.containsKey(extraEmptyPageId)) {
+ insertNewWorkspaceScreen(extraEmptyPageId);
+ }
+ });
+ }
+
+ /**
+ * 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
+ * two extra screens.
+ **/
+ public void addExtraEmptyScreens() {
+ forEachExtraEmptyPageId(extraEmptyPageId -> {
+ if (!mWorkspaceScreens.containsKey(extraEmptyPageId)) {
+ insertNewWorkspaceScreen(extraEmptyPageId);
+ }
+ });
+ }
+
+ /**
+ * Calls the consumer with all the necessary extra empty page IDs.
+ * On a normal one panel Workspace that means only EXTRA_EMPTY_SCREEN_ID,
+ * but in a two panel scenario this also includes EXTRA_EMPTY_SCREEN_SECOND_ID.
+ */
+ private void forEachExtraEmptyPageId(Consumer<Integer> callback) {
+ callback.accept(EXTRA_EMPTY_SCREEN_ID);
+ if (isTwoPanelEnabled()) {
+ callback.accept(EXTRA_EMPTY_SCREEN_SECOND_ID);
}
}
- public boolean addExtraEmptyScreen() {
- if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
- insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
- return true;
- }
- return false;
- }
-
+ /**
+ * If two panel home is enabled we convert the last two screens that are visible at the same
+ * time. In other cases we only convert the last page.
+ */
private void convertFinalScreenToEmptyScreenIfNecessary() {
if (mLauncher.isWorkspaceLoading()) {
// Invalid and dangerous operation if workspace is loading
return;
}
- if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
- int finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
+ int panelCount = getPanelCount();
+ if (hasExtraEmptyScreens() || mScreenOrder.size() < panelCount) {
+ return;
+ }
- CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
+ SparseArray<CellLayout> finalScreens = new SparseArray<>();
- // If the final screen is empty, convert it to the extra empty screen
- if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
- !finalScreen.isDropPending()) {
- mWorkspaceScreens.remove(finalScreenId);
- mScreenOrder.removeValue(finalScreenId);
+ int pageCount = mScreenOrder.size();
+ // First we add the last page(s) to the finalScreens collection. The number of final pages
+ // depends on the panel count.
+ for (int pageIndex = pageCount - panelCount; pageIndex < pageCount; pageIndex++) {
+ int screenId = mScreenOrder.get(pageIndex);
+ CellLayout screen = mWorkspaceScreens.get(screenId);
+ if (screen == null || screen.getShortcutsAndWidgets().getChildCount() != 0
+ || screen.isDropPending()) {
+ // Final screen doesn't exist or it isn't empty or there's a pending drop
+ return;
+ }
+ finalScreens.append(screenId, screen);
+ }
- // if this is the last screen, convert it to the empty screen
- mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
- mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
+ // Then we remove the final screens from the collections (but not from the view hierarchy)
+ // and we store them as extra empty screens.
+ for (int i = 0; i < finalScreens.size(); i++) {
+ int screenId = finalScreens.keyAt(i);
+ CellLayout screen = finalScreens.get(screenId);
+
+ mWorkspaceScreens.remove(screenId);
+ mScreenOrder.removeValue(screenId);
+
+ int newScreenId = mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)
+ ? EXTRA_EMPTY_SCREEN_SECOND_ID : EXTRA_EMPTY_SCREEN_ID;
+ mWorkspaceScreens.put(newScreenId, screen);
+ mScreenOrder.add(newScreenId);
}
}
@@ -703,6 +769,23 @@
removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null);
}
+ /**
+ * The purpose of this method is to remove empty pages from Workspace.
+ * Empty page(s) from the end of mWorkspaceScreens will always be removed. The pages with
+ * ID = Workspace.EXTRA_EMPTY_SCREEN_IDS will be removed if there are other non-empty pages.
+ * If there are no more non-empty pages left, extra empty page(s) will either stay or get added.
+ *
+ * If stripEmptyScreens is true, all empty pages (not just the ones on the end) will be removed
+ * from the Workspace, and if there are no more pages left then extra empty page(s) will be
+ * added.
+ *
+ * The number of extra empty pages is equal to what getPanelCount() returns.
+ *
+ * After the method returns the possible pages are:
+ * stripEmptyScreens = true : [non-empty pages, extra empty page(s) alone]
+ * stripEmptyScreens = false : [non-empty pages, empty pages (not in the end),
+ * extra empty page(s) alone]
+ */
public void removeExtraEmptyScreenDelayed(
int delay, boolean stripEmptyScreens, Runnable onComplete) {
if (mLauncher.isWorkspaceLoading()) {
@@ -716,18 +799,26 @@
return;
}
+ // First we convert the last page to an extra page if the last page is empty
+ // and we don't already have an extra page.
convertFinalScreenToEmptyScreenIfNecessary();
- if (hasExtraEmptyScreen()) {
- removeView(mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID));
+ // Then we remove the extra page(s) if they are not the only pages left in Workspace.
+ if (hasExtraEmptyScreens()) {
+ forEachExtraEmptyPageId(extraEmptyPageId -> {
+ removeView(mWorkspaceScreens.get(extraEmptyPageId));
+ mWorkspaceScreens.remove(extraEmptyPageId);
+ mScreenOrder.removeValue(extraEmptyPageId);
+ });
+
setCurrentPage(getNextPage());
- mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
- mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
// Update the page indicator to reflect the removed page.
showPageIndicatorAtCurrentScroll();
}
if (stripEmptyScreens) {
+ // This will remove all empty pages from the Workspace. If there are no more pages left,
+ // it will add extra page(s) so that users can put items on at least one page.
stripEmptyScreens();
}
@@ -736,27 +827,51 @@
}
}
- public boolean hasExtraEmptyScreen() {
- return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && getChildCount() > 1;
+ public boolean hasExtraEmptyScreens() {
+ return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)
+ && getChildCount() > getPanelCount()
+ && (!isTwoPanelEnabled()
+ || mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_SECOND_ID));
}
- public int commitExtraEmptyScreen() {
+ /**
+ * Commits the extra empty pages then returns the screen ids of those new screens.
+ * Usually there's only one extra empty screen, but when two panel home is enabled we commit
+ * two extra screens.
+ *
+ * Returns an empty IntSet in case we cannot commit any new screens.
+ */
+ public IntSet commitExtraEmptyScreens() {
if (mLauncher.isWorkspaceLoading()) {
// Invalid and dangerous operation if workspace is loading
- return -1;
+ return new IntSet();
}
- CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
- mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
- mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
+ IntSet extraEmptyPageIds = new IntSet();
+ forEachExtraEmptyPageId(extraEmptyPageId ->
+ extraEmptyPageIds.add(commitExtraEmptyScreen(extraEmptyPageId)));
- int newId = LauncherSettings.Settings.call(getContext().getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
- mWorkspaceScreens.put(newId, cl);
- mScreenOrder.add(newId);
+ return extraEmptyPageIds;
+ }
- return newId;
+ private int commitExtraEmptyScreen(int emptyScreenId) {
+ CellLayout cl = mWorkspaceScreens.get(emptyScreenId);
+ mWorkspaceScreens.remove(emptyScreenId);
+ mScreenOrder.removeValue(emptyScreenId);
+
+ int newScreenId = -1;
+ // Launcher database isn't aware of empty pages that are already bound, so we need to
+ // skip those IDs manually.
+ while (newScreenId == -1 || mWorkspaceScreens.containsKey(newScreenId)) {
+ newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ }
+
+ mWorkspaceScreens.put(newScreenId, cl);
+ mScreenOrder.add(newScreenId);
+
+ return newScreenId;
}
@Override
@@ -801,6 +916,17 @@
return mScreenOrder;
}
+ /**
+ * Returns the page that is shown together with the given page when two panel is enabled.
+ */
+ public int getPagePair(int page) {
+ if (page % 2 == 0) {
+ return page + 1;
+ } else {
+ return page - 1;
+ }
+ }
+
public void stripEmptyScreens() {
if (mLauncher.isWorkspaceLoading()) {
// Don't strip empty screens if the workspace is still loading.
@@ -826,9 +952,26 @@
}
}
- // We enforce at least one page to add new items to. In the case that we remove the last
- // such screen, we convert the last screen to the empty screen
- int minScreens = 1;
+ // When two panel home is enabled we only remove an empty page if both visible pages are
+ // empty.
+ if (isTwoPanelEnabled()) {
+ // We go through all the pages that were marked as removable and check their page pair
+ Iterator<Integer> removeScreensIterator = removeScreens.iterator();
+ while (removeScreensIterator.hasNext()) {
+ int pageToRemove = removeScreensIterator.next();
+ int pagePair = getPagePair(pageToRemove);
+ if (!removeScreens.contains(pagePair)) {
+ // The page pair isn't empty so we want to remove the current page from the
+ // removable pages' collection
+ removeScreensIterator.remove();
+ }
+ }
+ }
+
+ // We enforce at least one page (two pages on two panel home) to add new items to.
+ // In the case that we remove the last such screen(s), we convert the last screen(s)
+ // to the empty screen(s)
+ int minScreens = getPanelCount();
int pageShift = 0;
for (int i = 0; i < removeScreens.size(); i++) {
@@ -838,14 +981,21 @@
mScreenOrder.removeValue(id);
if (getChildCount() > minScreens) {
+ // If this isn't the last page, just remove it
if (indexOfChild(cl) < currentPage) {
pageShift++;
}
removeView(cl);
} else {
- // if this is the last screen, convert it to the empty screen
- mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
- mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
+ // The last page(s) should be converted into extra empty page(s)
+ int extraScreenId = isTwoPanelEnabled() && id % 2 == 1
+ // This is the right panel in a two panel scenario
+ ? EXTRA_EMPTY_SCREEN_SECOND_ID
+ // This is either the last screen in a one panel scenario, or the left panel
+ // in a two panel scenario when there are only two empty pages left
+ : EXTRA_EMPTY_SCREEN_ID;
+ mWorkspaceScreens.put(extraScreenId, cl);
+ mScreenOrder.add(extraScreenId);
}
}
@@ -891,17 +1041,25 @@
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mXDown = ev.getX();
- mYDown = ev.getY();
+ protected void updateIsBeingDraggedOnTouchDown(MotionEvent ev) {
+ super.updateIsBeingDraggedOnTouchDown(ev);
+
+ mXDown = ev.getX();
+ mYDown = ev.getY();
+ if (mQsb != null) {
+ mTempFXY[0] = mXDown + getScrollX();
+ mTempFXY[1] = mYDown + getScrollY();
+ Utilities.mapCoordInSelfToDescendant(mQsb, this, mTempFXY);
+ mIsEventOverQsb = mQsb.getLeft() <= mTempFXY[0] && mQsb.getRight() >= mTempFXY[0]
+ && mQsb.getTop() <= mTempFXY[1] && mQsb.getBottom() >= mTempFXY[1];
+ } else {
+ mIsEventOverQsb = false;
}
- return super.onInterceptTouchEvent(ev);
}
@Override
protected void determineScrollingStart(MotionEvent ev) {
- if (!isFinishedSwitchingState()) return;
+ if (!isFinishedSwitchingState() || mIsEventOverQsb) return;
float deltaX = ev.getX() - mXDown;
float absDeltaX = Math.abs(deltaX);
@@ -1609,14 +1767,14 @@
// Don't accept the drop if there's no room for the item
if (!foundCell) {
- onNoCellFound(dropTargetLayout);
+ onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
return false;
}
}
int screenId = getIdForScreen(dropTargetLayout);
- if (screenId == EXTRA_EMPTY_SCREEN_ID) {
- commitExtraEmptyScreen();
+ if (Workspace.EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
+ commitExtraEmptyScreens();
}
return true;
@@ -1911,7 +2069,7 @@
lp.cellX, lp.cellY, item.spanX, item.spanY);
} else {
if (!returnToOriginalCellToPreventShuffling) {
- onNoCellFound(dropTargetLayout);
+ onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
}
if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
@@ -1979,10 +2137,16 @@
}
}
- public void onNoCellFound(View dropTargetLayout) {
+ public void onNoCellFound(
+ View dropTargetLayout, ItemInfo itemInfo, @Nullable InstanceId logInstanceId) {
int strId = mLauncher.isHotseatLayout(dropTargetLayout)
? R.string.hotseat_out_of_space : R.string.out_of_space;
Toast.makeText(mLauncher, mLauncher.getString(strId), Toast.LENGTH_SHORT).show();
+ StatsLogManager.StatsLogger logger = mStatsLogManager.logger().withItemInfo(itemInfo);
+ if (logInstanceId != null) {
+ logger = logger.withInstanceId(logInstanceId);
+ }
+ logger.log(LauncherEvent.LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE);
}
/**
@@ -2286,21 +2450,32 @@
}
int nextPage = getNextPage();
- if (layout == null && !isPageInTransition()) {
- layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), Math.min(centerX, d.x), d.y);
+ IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage + 1);
+ if (isTwoPanelEnabled()) {
+ // If two panel is enabled, users can also drag items to nextPage + 2
+ pageIndexesToVerify.add(nextPage + 2);
}
- if (layout == null && !isPageInTransition()) {
- layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), Math.max(centerX, d.x), d.y);
+ int touchX = (int) Math.min(centerX, d.x);
+ int touchY = d.y;
+
+ // Go through the pages and check if the dragged item is inside one of them
+ for (int pageIndex : pageIndexesToVerify) {
+ if (layout != null || isPageInTransition()) {
+ break;
+ }
+ layout = verifyInsidePage(pageIndex, touchX, touchY);
}
- // If two panel is enabled, users can also drag items to currentPage + 2
- if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) {
- layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), Math.max(centerX, d.x), d.y);
- }
-
- // Always pick the current page.
+ // If the dragged item isn't located in one of the pages above, the icon will stay on the
+ // current screen. For two panel pick the closest panel on the current screen,
+ // on one panel just choose the current page.
if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
+ if (isTwoPanelEnabled()) {
+ nextPage = getScreenCenter(getScrollX()) > touchX
+ ? (mIsRtl ? nextPage + 1 : nextPage) // left side
+ : (mIsRtl ? nextPage : nextPage + 1); // right side
+ }
layout = (CellLayout) getChildAt(nextPage);
}
if (layout != mDragTargetLayout) {
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 326e3c3..7e6e1b6 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -23,6 +23,7 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.util.IntSet;
public interface WorkspaceLayoutManager {
@@ -30,10 +31,16 @@
// The screen id used for the empty screen always present at the end.
int EXTRA_EMPTY_SCREEN_ID = -201;
+ // The screen id used for the second empty screen always present at the end for two panel home.
+ int EXTRA_EMPTY_SCREEN_SECOND_ID = -200;
+ // The screen ids used for the empty screens at the end of the workspaces.
+ IntSet EXTRA_EMPTY_SCREEN_IDS =
+ IntSet.wrap(EXTRA_EMPTY_SCREEN_ID, EXTRA_EMPTY_SCREEN_SECOND_ID);
+
// The is the first screen. It is always present, even if its empty.
int FIRST_SCREEN_ID = 0;
- // This panel is shown on the first page if the panel count is greater than 1.
- int LEFT_PANEL_ID = -777;
+ // This is the second page. On two panel home it is always present, even if its empty.
+ int SECOND_SCREEN_ID = 1;
/**
* At bind time, we use the rank (screenId) to compute x and y for hotseat items.
@@ -81,9 +88,9 @@
return;
}
}
- if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+ if (EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
// This should never happen
- throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
+ throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
}
final CellLayout layout;
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0fb5e77..2032b26 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -45,6 +45,7 @@
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.OptionsPopupView;
@@ -188,8 +189,7 @@
private boolean itemSupportsAccessibleDrag(ItemInfo item) {
if (item instanceof WorkspaceItemInfo) {
// Support the action unless the item is in a context menu.
- return (item.screenId >= 0 || item.screenId == Workspace.LEFT_PANEL_ID)
- && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
return (item instanceof LauncherAppWidgetInfo)
|| (item instanceof FolderInfo);
@@ -222,6 +222,9 @@
} else if (action == ADD_TO_WORKSPACE) {
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
+ if (screenId == -1) {
+ return false;
+ }
mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
if (item instanceof AppInfo) {
WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem();
@@ -251,6 +254,9 @@
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
+ if (screenId == -1) {
+ return false;
+ }
mLauncher.getModelWriter().moveItemInDatabase(info,
Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
@@ -490,8 +496,14 @@
return screenId;
}
- workspace.addExtraEmptyScreen();
- screenId = workspace.commitExtraEmptyScreen();
+ workspace.addExtraEmptyScreens();
+ IntSet emptyScreenIds = workspace.commitExtraEmptyScreens();
+ if (emptyScreenIds.isEmpty()) {
+ // Couldn't create extra empty screens for some reason (e.g. Workspace is loading)
+ return -1;
+ }
+
+ screenId = emptyScreenIds.getArray().get(0);
layout = workspace.getScreenWithId(screenId);
found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index f96afa8..bf5a24b 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -68,6 +68,9 @@
final WorkspaceItemInfo info = ((DeepShortcutView) host.getParent()).getFinalInfo();
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
+ if (screenId == -1) {
+ return false;
+ }
mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
mLauncher.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index d2c71b2..e779ee8 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -59,6 +59,7 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.R;
@@ -118,7 +119,8 @@
private SpannableStringBuilder mSearchQueryBuilder = null;
protected boolean mUsingTabs;
- private boolean mSearchModeWhileUsingTabs = false;
+ private boolean mIsSearching;
+ private boolean mHasWorkApps;
protected RecyclerViewFastScroller mTouchHandler;
protected final Point mFastScrollerOffset = new Point();
@@ -132,6 +134,7 @@
private final float mHeaderThreshold;
private ScrimView mScrimView;
private int mHeaderColor;
+ private int mTabsProtectionAlpha;
public AllAppsContainerView(Context context) {
this(context, null);
@@ -189,7 +192,7 @@
int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0);
if (currentPage != 0 && mViewPager != null) {
mViewPager.setCurrentPage(currentPage);
- rebindAdapters(true);
+ rebindAdapters();
} else {
reset(true);
}
@@ -243,9 +246,10 @@
break;
}
}
+ mHasWorkApps = hasWorkApps;
if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) {
- rebindAdapters(hasWorkApps);
- if (hasWorkApps) {
+ rebindAdapters();
+ if (mHasWorkApps) {
resetWorkProfile();
}
}
@@ -322,6 +326,8 @@
mViewPager.getNextPage() == 0
? R.string.all_apps_button_personal_label
: R.string.all_apps_button_work_label;
+ } else if (mIsSearching) {
+ descriptionRes = R.string.all_apps_search_results;
} else {
descriptionRes = R.string.all_apps_button_label;
}
@@ -370,7 +376,7 @@
});
mHeader = findViewById(R.id.all_apps_header);
- rebindAdapters(mUsingTabs, true /* force */);
+ rebindAdapters(true /* force */);
mSearchContainer = findViewById(R.id.search_container_all_apps);
mSearchUiManager = (SearchUiManager) mSearchContainer;
@@ -439,11 +445,12 @@
}
}
- private void rebindAdapters(boolean showTabs) {
- rebindAdapters(showTabs, false /* force */);
+ private void rebindAdapters() {
+ rebindAdapters(false /* force */);
}
- protected void rebindAdapters(boolean showTabs, boolean force) {
+ protected void rebindAdapters(boolean force) {
+ boolean showTabs = mHasWorkApps && !mIsSearching;
if (showTabs == mUsingTabs && !force) {
return;
}
@@ -454,7 +461,6 @@
mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
if (mUsingTabs) {
- setupWorkToggle();
mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
mAH[AdapterHolder.WORK].recyclerView.setId(R.id.apps_list_view_work);
@@ -485,6 +491,7 @@
}
private void setupWorkToggle() {
+ removeWorkToggle();
if (Utilities.ATLEAST_P) {
mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
R.layout.work_mode_fab, this, false);
@@ -497,10 +504,20 @@
}
}
+ private void removeWorkToggle() {
+ if (mWorkModeSwitch == null) return;
+ if (mWorkModeSwitch.getParent() == this) {
+ this.removeView(mWorkModeSwitch);
+ }
+ mWorkModeSwitch = null;
+ }
+
private void replaceRVContainer(boolean showTabs) {
for (int i = 0; i < mAH.length; i++) {
- if (mAH[i].recyclerView != null) {
- mAH[i].recyclerView.setLayoutManager(null);
+ AllAppsRecyclerView rv = mAH[i].recyclerView;
+ if (rv != null) {
+ rv.setLayoutManager(null);
+ rv.setAdapter(null);
}
}
View oldView = getRecyclerViewContainer();
@@ -517,8 +534,10 @@
mViewPager = (AllAppsPagedView) newView;
mViewPager.initParentViews(this);
mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
+ setupWorkToggle();
} else {
mViewPager = null;
+ removeWorkToggle();
}
}
@@ -537,14 +556,6 @@
mWorkModeSwitch.setWorkTabVisible(currentActivePage == AdapterHolder.WORK
&& mAllAppsStore.hasModelFlag(
FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
-
- if (currentActivePage == AdapterHolder.WORK) {
- if (mWorkModeSwitch.getParent() == null) {
- addView(mWorkModeSwitch);
- }
- } else {
- removeView(mWorkModeSwitch);
- }
}
}
@@ -621,18 +632,15 @@
for (int i = 0; i < mAH.length; i++) {
mAH[i].adapter.setLastSearchQuery(query);
}
- if (mUsingTabs) {
- mSearchModeWhileUsingTabs = true;
- rebindAdapters(false); // hide tabs
- }
+ mIsSearching = true;
+ rebindAdapters();
mHeader.setCollapsed(true);
}
public void onClearSearchResult() {
- if (mSearchModeWhileUsingTabs) {
- rebindAdapters(true); // show tabs
- mSearchModeWhileUsingTabs = false;
- }
+ mIsSearching = false;
+ rebindAdapters();
+ getActiveRecyclerView().scrollToTop();
}
public void onSearchResultsChanged() {
@@ -706,13 +714,12 @@
mHeaderPaint.setColor(mHeaderColor);
mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
if (mHeaderPaint.getColor() != mScrimColor && mHeaderPaint.getColor() != 0) {
- int bottom = mUsingTabs && mHeader.mHeaderCollapsed ? mHeader.getVisibleBottomBound()
- : mSearchContainer.getBottom();
- canvas.drawRect(0, 0, canvas.getWidth(), bottom + getTranslationY(),
- mHeaderPaint);
-
- if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && getTranslationY() == 0) {
- mSearchUiManager.getEditText().setBackground(null);
+ int bottom = (int) (mSearchContainer.getBottom() + getTranslationY());
+ canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
+ int tabsHeight = getFloatingHeaderView().getPeripheralProtectionHeight();
+ if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
+ mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
+ canvas.drawRect(0, bottom, canvas.getWidth(), bottom + tabsHeight, mHeaderPaint);
}
}
}
@@ -792,18 +799,29 @@
protected void updateHeaderScroll(int scrolledOffset) {
- float prog = Math.max(0, Math.min(1, (float) scrolledOffset / mHeaderThreshold));
+
+ float prog = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
int viewBG = ColorUtils.blendARGB(mScrimColor, mHeaderProtectionColor, prog);
int headerColor = ColorUtils.setAlphaComponent(viewBG,
(int) (getSearchView().getAlpha() * 255));
- if (headerColor != mHeaderColor) {
+ int tabsAlpha = mHeader.getPeripheralProtectionHeight() == 0 ? 0
+ : (int) (Utilities.boundToRange(
+ (scrolledOffset + mHeader.mSnappedScrolledY) / mHeaderThreshold, 0f, 1f)
+ * 255);
+ if (headerColor != mHeaderColor || mTabsProtectionAlpha != tabsAlpha) {
mHeaderColor = headerColor;
- getSearchView().setBackgroundColor(viewBG);
- getFloatingHeaderView().setHeaderColor(viewBG);
+ mTabsProtectionAlpha = tabsAlpha;
invalidateHeader();
- if (scrolledOffset == 0 && mSearchUiManager.getEditText() != null) {
- mSearchUiManager.getEditText().show();
+ }
+ if (mSearchUiManager.getEditText() != null) {
+ ExtendedEditText editText = mSearchUiManager.getEditText();
+ boolean bgVisible = editText.getBackgroundVisibility();
+ if (scrolledOffset == 0 && !mIsSearching) {
+ bgVisible = true;
+ } else if (scrolledOffset > mHeaderThreshold) {
+ bgVisible = false;
}
+ editText.setBackgroundVisibility(bgVisible, 1 - prog);
}
}
@@ -811,7 +829,7 @@
* redraws header protection
*/
public void invalidateHeader() {
- if (mScrimView != null && FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ if (mScrimView != null && mHeader.isHeaderProtectionSupported()) {
mScrimView.invalidate();
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 2c84a3d..bddbbd0 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -63,6 +63,13 @@
private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
private final AllAppsFastScrollHelper mFastScrollHelper;
+
+ private final AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
+ public void onChanged() {
+ mCachedScrollPositions.clear();
+ }
+ };
+
// The empty-search result background
private AllAppsBackgroundDrawable mEmptySearchBackground;
private int mEmptySearchBackgroundTopOffset;
@@ -247,12 +254,13 @@
@Override
public void setAdapter(Adapter adapter) {
+ if (getAdapter() != null) {
+ getAdapter().unregisterAdapterDataObserver(mObserver);
+ }
super.setAdapter(adapter);
- adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
- public void onChanged() {
- mCachedScrollPositions.clear();
- }
- });
+ if (adapter != null) {
+ adapter.registerAdapterDataObserver(mObserver);
+ }
}
@Override
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 8ea83d5..debb5b2 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -17,9 +17,6 @@
import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -50,11 +47,10 @@
ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow>, Insettable,
OnHeightUpdatedListener {
- private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ private final Rect mRVClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ private final Rect mHeaderClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
- private final ValueAnimator mHeaderAnimator = ValueAnimator.ofInt(0, 1).setDuration(100);
private final Point mTempOffset = new Point();
- private final Paint mBGPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RecyclerView.OnScrollListener mOnScrollListener =
new RecyclerView.OnScrollListener() {
@Override
@@ -82,19 +78,19 @@
}
};
- private final int mHeaderTopPadding;
-
protected final Map<AllAppsRow, PluginHeaderRow> mPluginRows = new ArrayMap<>();
+ private final int mHeaderTopPadding;
+ private final boolean mHeaderProtectionSupported;
+
protected ViewGroup mTabLayout;
private AllAppsRecyclerView mMainRV;
private AllAppsRecyclerView mWorkRV;
private AllAppsRecyclerView mCurrentRV;
private ViewGroup mParent;
public boolean mHeaderCollapsed;
- private int mSnappedScrolledY;
+ protected int mSnappedScrolledY;
private int mTranslationY;
- private int mHeaderColor;
private boolean mForwardToRecyclerView;
@@ -120,6 +116,8 @@
super(context, attrs);
mHeaderTopPadding = context.getResources()
.getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
+ mHeaderProtectionSupported = context.getResources().getBoolean(
+ R.bool.config_header_protection_supported);
}
@Override
@@ -138,7 +136,6 @@
}
mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
mAllRows = mFixedRows;
- mHeaderAnimator.addUpdateListener(valueAnimator -> invalidate());
}
@Override
@@ -285,7 +282,7 @@
mHeaderCollapsed = false;
}
mTranslationY = currentScrollY;
- } else if (!mHeaderCollapsed) {
+ } else {
mTranslationY = currentScrollY - mSnappedScrolledY - mMaxTranslation;
// update state vars
@@ -295,31 +292,10 @@
} else if (mTranslationY <= -mMaxTranslation) { // hide or stay hidden
mHeaderCollapsed = true;
mSnappedScrolledY = -mMaxTranslation;
- mHeaderAnimator.setCurrentFraction(0);
- mHeaderAnimator.start();
}
}
}
- /**
- * Set current header protection background color
- */
- public void setHeaderColor(int color) {
- mHeaderColor = color;
- invalidate();
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (mHeaderCollapsed && !mCollapsed && mTabLayout.getVisibility() == VISIBLE
- && mHeaderColor != Color.TRANSPARENT && FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- mBGPaint.setColor(mHeaderColor);
- mBGPaint.setAlpha((int) (255 * mHeaderAnimator.getAnimatedFraction()));
- canvas.drawRect(0, 0, getWidth(), getHeight() + mTranslationY, mBGPaint);
- }
- super.dispatchDraw(canvas);
- }
-
protected void applyVerticalMove() {
int uncappedTranslationY = mTranslationY;
mTranslationY = Math.max(mTranslationY, -mMaxTranslation);
@@ -336,11 +312,15 @@
}
mTabLayout.setTranslationY(mTranslationY);
- mClip.top = mMaxTranslation + mTranslationY;
+
+ int clipHeight = mHeaderTopPadding - getPaddingBottom();
+ mRVClip.top = mTabsHidden ? clipHeight : 0;
+ mHeaderClip.top = clipHeight;
// clipping on a draw might cause additional redraw
- mMainRV.setClipBounds(mClip);
+ setClipBounds(mHeaderClip);
+ mMainRV.setClipBounds(mRVClip);
if (mWorkRV != null) {
- mWorkRV.setClipBounds(mClip);
+ mWorkRV.setClipBounds(mRVClip);
}
}
@@ -421,6 +401,10 @@
return false;
}
+ public boolean isHeaderProtectionSupported() {
+ return mHeaderProtectionSupported;
+ }
+
@Override
public boolean hasOverlappingRendering() {
return false;
@@ -444,10 +428,19 @@
}
/**
- * Returns visible height of FloatingHeaderView contents
+ * Returns visible height of FloatingHeaderView contents requiring header protection
*/
- public int getVisibleBottomBound() {
- return getBottom() + mTranslationY;
+ public int getPeripheralProtectionHeight() {
+ if (!mHeaderProtectionSupported) {
+ return 0;
+ }
+
+ // we only want to show protection when work tab is available and header is either
+ // collapsed or animating to/from collapsed state
+ if (mTabsHidden || !mHeaderCollapsed) {
+ return 0;
+ }
+ return Math.max(getHeight() - getPaddingTop() + mTranslationY, 0);
}
}
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index f64b7cb..b6dcec6 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -22,7 +22,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.statemanager.StateManager.StateListener;
/**
* AllAppsContainerView with launcher specific callbacks
@@ -31,8 +30,6 @@
private final Launcher mLauncher;
- private StateListener<LauncherState> mWorkTabListener;
-
public LauncherAllAppsContainerView(Context context) {
this(context, null);
}
@@ -75,14 +72,6 @@
}
@Override
- public void setupHeader() {
- super.setupHeader();
- if (mWorkTabListener != null && !mUsingTabs) {
- mLauncher.getStateManager().removeStateListener(mWorkTabListener);
- }
- }
-
- @Override
public void onActivePageChanged(int currentActivePage) {
super.onActivePageChanged(currentActivePage);
}
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index a800d34..5d3af08 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -51,6 +51,7 @@
@Nullable
private KeyboardInsetAnimationCallback mKeyboardInsetAnimationCallback;
+ private boolean mWorkTabVisible;
public WorkModeSwitch(Context context) {
this(context, null, 0);
@@ -91,11 +92,10 @@
*/
public void setWorkTabVisible(boolean workTabVisible) {
clearAnimation();
- if (workTabVisible) {
+ mWorkTabVisible = workTabVisible;
+ if (workTabVisible && mWorkEnabled) {
setEnabled(true);
- if (mWorkEnabled) {
- setVisibility(VISIBLE);
- }
+ setVisibility(VISIBLE);
setAlpha(0);
animate().alpha(1).start();
} else {
@@ -105,7 +105,7 @@
@Override
public void onClick(View view) {
- if (Utilities.ATLEAST_P) {
+ if (Utilities.ATLEAST_P && mWorkTabVisible) {
setEnabled(false);
Launcher.fromContext(getContext()).getStatsLogManager().logger().log(
LAUNCHER_TURN_OFF_WORK_APPS_TAP);
@@ -137,7 +137,7 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (Utilities.ATLEAST_R) {
+ if (Utilities.ATLEAST_R && mWorkTabVisible) {
setTranslationY(0);
if (insets.isVisible(WindowInsets.Type.ime())) {
Insets keyboardInsets = insets.getInsets(WindowInsets.Type.ime());
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index da701a8..f091262 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -219,6 +219,9 @@
public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
"ENABLE_TASKBAR", true, "Allows a system Taskbar to be shown on larger devices.");
+ public static final BooleanFlag ENABLE_TASKBAR_EDU = getDebugFlag("ENABLE_TASKBAR_EDU", true,
+ "Enables showing taskbar education the first time an app is opened.");
+
public static final BooleanFlag ENABLE_OVERVIEW_GRID = getDebugFlag(
"ENABLE_OVERVIEW_GRID", true, "Uses grid overview layout. "
+ "Only applicable on large screen devices.");
@@ -227,12 +230,16 @@
"ENABLE_TWO_PANEL_HOME", true,
"Uses two panel on home screen. Only applicable on large screen devices.");
+ public static final BooleanFlag ENABLE_TWO_PANEL_HOME_IN_PORTRAIT = getDebugFlag(
+ "ENABLE_TWO_PANEL_HOME_IN_PORTRAIT", true,
+ "Uses two panel on home screen in portrait if ENABLE_TWO_PANEL_HOME is enabled.");
+
public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(
"ENABLE_SCRIM_FOR_APP_LAUNCH", false,
"Enables scrim during app launch animation.");
public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
- "ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
+ "ENABLE_SPLIT_SELECT", true, "Uses new split screen selection overview UI");
public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
"ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 92ed18a..466b268 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -286,9 +286,7 @@
@Override
protected void onPostExecute(WidgetItem item) {
- mWidgetCell.setPreviewSize(item);
- mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
- mWidgetCell.ensurePreview();
+ mWidgetCell.applyFromCellItem(item);
}
}.executeOnExecutor(MODEL_EXECUTOR);
// TODO: Create a worker looper executor and reuse that everywhere.
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1e0edac..fdb2799 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -34,6 +35,7 @@
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.ActivityContext;
@@ -146,6 +148,9 @@
float initialDragViewScale,
float dragViewScaleOnDrop,
DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "4");
+ }
return startDrag(drawable, /* view= */ null, originalView, dragLayerX, dragLayerY,
source, dragInfo, dragOffset, dragRegion, initialDragViewScale, dragViewScaleOnDrop,
options);
@@ -203,6 +208,9 @@
DragOptions options);
protected void callOnDragStart() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "6");
+ }
if (mOptions.preDragCondition != null) {
mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
}
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index 0e8b0a5..dcbfa50 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -24,6 +24,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
@@ -36,6 +37,7 @@
import com.android.launcher3.R;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.testing.TestProtocol;
/**
* Drag controller for Launcher activity
@@ -65,6 +67,9 @@
float initialDragViewScale,
float dragViewScaleOnDrop,
DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "5");
+ }
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7187188..879739f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -276,15 +276,19 @@
mPageIndicator = findViewById(R.id.folder_page_indicator);
mFolderName = findViewById(R.id.folder_name);
mFolderName.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.folderLabelTextSizePx);
- mFolderName.setOnBackKeyListener(this);
- mFolderName.setOnFocusChangeListener(this);
- mFolderName.setOnEditorActionListener(this);
- mFolderName.setSelectAllOnFocus(true);
- mFolderName.setInputType(mFolderName.getInputType()
- & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
- | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
- | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
- mFolderName.forceDisableSuggestions(true);
+ if (mActivityContext.supportsIme()) {
+ mFolderName.setOnBackKeyListener(this);
+ mFolderName.setOnFocusChangeListener(this);
+ mFolderName.setOnEditorActionListener(this);
+ mFolderName.setSelectAllOnFocus(true);
+ mFolderName.setInputType(mFolderName.getInputType()
+ & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+ | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ mFolderName.forceDisableSuggestions(true);
+ } else {
+ mFolderName.setEnabled(false);
+ }
mFooter = findViewById(R.id.folder_footer);
mFooterHeight = getResources().getDimensionPixelSize(R.dimen.folder_label_height);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 60d8cdb..439df80 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -132,6 +132,9 @@
private Rect mTouchArea = new Rect();
+ private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
+ private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
@@ -765,8 +768,11 @@
}
private void updateTranslation() {
- super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
- super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+ + mTranslationForMoveFromCenterAnimation.x
+ + mTranslationXForTaskbarAlignmentAnimation);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+ + mTranslationForMoveFromCenterAnimation.y);
}
public void setReorderBounceOffset(float x, float y) {
@@ -778,6 +784,29 @@
offset.set(mTranslationForReorderBounce);
}
+ /**
+ * Sets translationX value for taskbar to launcher alignment animation
+ */
+ public void setTranslationForTaskbarAlignmentAnimation(float translationX) {
+ mTranslationXForTaskbarAlignmentAnimation = translationX;
+ updateTranslation();
+ }
+
+ /**
+ * Returns translation values for taskbar to launcher alignment animation
+ */
+ public float getTranslationXForTaskbarAlignmentAnimation() {
+ return mTranslationXForTaskbarAlignmentAnimation;
+ }
+
+ /**
+ * Sets translation values for move from center animation
+ */
+ public void setTranslationForMoveFromCenterAnimation(float x, float y) {
+ mTranslationForMoveFromCenterAnimation.set(x, y);
+ updateTranslation();
+ }
+
@Override
public void setReorderPreviewOffset(float x, float y) {
mTranslationForReorderPreview.set(x, y);
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index e4f5539..fc8d855 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -9,7 +9,6 @@
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.pm.PackageManager;
-import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
@@ -23,23 +22,13 @@
import android.os.Messenger;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Xml;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Executors;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
/**
* Exposes various launcher grid options and allows the caller to change them.
* APIs:
@@ -94,7 +83,7 @@
MatrixCursor cursor = new MatrixCursor(new String[] {
KEY_NAME, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT});
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
- for (GridOption gridOption : parseAllGridOptions()) {
+ for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
cursor.newRow()
.add(KEY_NAME, gridOption.name)
.add(KEY_ROWS, gridOption.numRows)
@@ -116,25 +105,6 @@
}
}
- private List<GridOption> parseAllGridOptions() {
- List<GridOption> result = new ArrayList<>();
- try (XmlResourceParser parser = getContext().getResources().getXml(R.xml.device_profiles)) {
- final int depth = parser.getDepth();
- int type;
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
- if ((type == XmlPullParser.START_TAG)
- && GridOption.TAG_NAME.equals(parser.getName())) {
- result.add(new GridOption(getContext(), Xml.asAttributeSet(parser)));
- }
- }
- } catch (IOException | XmlPullParserException e) {
- Log.e(TAG, "Error parsing device profile", e);
- return Collections.emptyList();
- }
- return result;
- }
-
@Override
public String getType(Uri uri) {
return "vnd.android.cursor.dir/launcher_grid";
@@ -155,9 +125,10 @@
switch (uri.getPath()) {
case KEY_DEFAULT_GRID: {
String gridName = values.getAsString(KEY_NAME);
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
// Verify that this is a valid grid option
GridOption match = null;
- for (GridOption option : parseAllGridOptions()) {
+ for (GridOption option : idp.parseAllGridOptions(getContext())) {
if (option.name.equals(gridName)) {
match = option;
break;
@@ -167,8 +138,7 @@
return 0;
}
- InvariantDeviceProfile.INSTANCE.get(getContext())
- .setCurrentGrid(getContext(), gridName);
+ idp.setCurrentGrid(getContext(), gridName);
return 1;
}
case ICON_THEMED:
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index bb726f8..94fc708 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -65,6 +65,7 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
@@ -86,7 +87,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.BaseLauncherAppWidgetHostView;
@@ -97,13 +98,10 @@
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
@@ -122,22 +120,16 @@
* Context used just for preview. It also provides a few objects (e.g. UserCache) just for
* preview purposes.
*/
- public static class PreviewContext extends ContextWrapper {
-
- private final Set<MainThreadInitializedObject> mAllowedObjects = new HashSet<>(
- Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
- LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
- CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE));
+ public static class PreviewContext extends SandboxContext {
private final InvariantDeviceProfile mIdp;
- private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
new ConcurrentLinkedQueue<>();
- private boolean mDestroyed = false;
-
public PreviewContext(Context base, InvariantDeviceProfile idp) {
- super(base);
+ super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
+ LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
+ CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE);
mIdp = idp;
mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp);
mObjectMap.put(LauncherAppState.INSTANCE,
@@ -145,37 +137,6 @@
}
- @Override
- public Context getApplicationContext() {
- return this;
- }
-
- public void onDestroy() {
- CustomWidgetManager.INSTANCE.get(this).onDestroy();
- LauncherAppState.INSTANCE.get(this).onTerminate();
- mDestroyed = true;
- }
-
- /**
- * Find a cached object from mObjectMap if we have already created one. If not, generate
- * an object using the provider.
- */
- public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
- MainThreadInitializedObject.ObjectProvider<T> provider) {
- if (FeatureFlags.IS_STUDIO_BUILD && mDestroyed) {
- throw new RuntimeException("Context already destroyed");
- }
- if (!mAllowedObjects.contains(mainThreadInitializedObject)) {
- throw new IllegalStateException("Leaking unknown objects");
- }
- if (mObjectMap.containsKey(mainThreadInitializedObject)) {
- return (T) mObjectMap.get(mainThreadInitializedObject);
- }
- T t = provider.get(this);
- mObjectMap.put(mainThreadInitializedObject, t);
- return t;
- }
-
public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) {
LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
if (launcherIconsForPreview != null) {
@@ -266,22 +227,22 @@
mHotseat = mRootView.findViewById(R.id.hotseat);
mHotseat.resetLayout(false);
- if (mDp.isTwoPanels) {
- CellLayout leftPanel = mRootView.findViewById(R.id.workspace_left);
- leftPanel.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
- mDp.workspacePadding.top,
- mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
- mDp.workspacePadding.bottom);
- mWorkspaceScreens.put(LEFT_PANEL_ID, leftPanel);
- }
-
CellLayout firstScreen = mRootView.findViewById(R.id.workspace);
firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
mDp.workspacePadding.top,
- mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+ mDp.workspacePadding.right,
mDp.workspacePadding.bottom);
mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen);
+ if (mDp.isTwoPanels) {
+ CellLayout rightPanel = mRootView.findViewById(R.id.workspace_right);
+ rightPanel.setPadding(mDp.workspacePadding.left,
+ mDp.workspacePadding.top,
+ mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+ mDp.workspacePadding.bottom);
+ mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel);
+ }
+
if (Utilities.ATLEAST_S) {
WallpaperColors wallpaperColors = wallpaperColorsOverride != null
? wallpaperColorsOverride
@@ -363,7 +324,9 @@
}
private void inflateAndAddFolder(FolderInfo info) {
- CellLayout screen = mWorkspaceScreens.get(info.screenId);
+ CellLayout screen = info.container == Favorites.CONTAINER_DESKTOP
+ ? mWorkspaceScreens.get(info.screenId)
+ : mHotseat;
FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, screen,
info);
addInScreenFromBind(folderIcon, info);
@@ -510,8 +473,8 @@
// Add first page QSB
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
- View qsb = mHomeElementInflater.inflate(
- R.layout.search_container_workspace, firstScreen, false);
+ View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen,
+ false);
CellLayout.LayoutParams lp =
new CellLayout.LayoutParams(0, 0, firstScreen.getCountX(), 1);
lp.canReorder = false;
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index c7448dc..2f3d5d8 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -41,7 +41,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
@@ -49,7 +48,6 @@
import com.android.launcher3.model.GridSizeMigrationTaskV2;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
-import com.android.launcher3.model.ModelPreload;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
@@ -150,7 +148,8 @@
inflationContext = new ContextThemeWrapper(context,
Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
} else {
- inflationContext = new ContextThemeWrapper(mContext, R.style.AppTheme);
+ inflationContext = new ContextThemeWrapper(mContext,
+ Themes.getActivityThemeRes(mContext));
}
if (migrated) {
@@ -164,11 +163,14 @@
@Override
public void run() {
DeviceProfile deviceProfile = mIdp.getDeviceProfile(previewContext);
- String query = (deviceProfile.isTwoPanels ? LauncherSettings.Favorites.SCREEN
- + " = " + Workspace.LEFT_PANEL_ID + " or " : "")
- + LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
+ String query =
+ LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
+ " or " + LauncherSettings.Favorites.CONTAINER + " = "
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ if (deviceProfile.isTwoPanels) {
+ query += " or " + LauncherSettings.Favorites.SCREEN + " = "
+ + Workspace.SECOND_SCREEN_ID;
+ }
loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
query);
@@ -179,18 +181,13 @@
}
}.run();
} else {
- new ModelPreload() {
-
- @Override
- public void onComplete(boolean isSuccess) {
- if (isSuccess) {
- MAIN_EXECUTOR.execute(() ->
- renderView(inflationContext, getBgDataModel(), null));
- } else {
- Log.e(TAG, "Model loading failed");
- }
+ LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
+ if (dataModel != null) {
+ MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null));
+ } else {
+ Log.e(TAG, "Model loading failed");
}
- }.start(inflationContext);
+ });
}
}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index cd13cd0..1a468ae 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -134,6 +134,9 @@
* Closes the cache DB. This will clear any in-memory cache.
*/
public void close() {
+ // This will clear all pending updates
+ getUpdateHandler();
+
mIconDb.close();
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 79e5b5d..5ed651f 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -76,6 +76,22 @@
}
public interface EventEnum {
+
+ /**
+ * Tag used to request new UI Event IDs via presubmit analysis.
+ *
+ * <p>Use RESERVE_NEW_UI_EVENT_ID as the constructor parameter for a new {@link EventEnum}
+ * to signal the presubmit analyzer to reserve a new ID for the event. The new ID will be
+ * returned as a Gerrit presubmit finding. Do not submit {@code RESERVE_NEW_UI_EVENT_ID} as
+ * the constructor parameter for any event.
+ *
+ * <pre>
+ * @UiEvent(doc = "Briefly describe the interaction when this event will be logged")
+ * UNIQUE_EVENT_NAME(RESERVE_NEW_UI_EVENT_ID);
+ * </pre>
+ */
+ int RESERVE_NEW_UI_EVENT_ID = Integer.MIN_VALUE; // Negative IDs are ignored by the logger.
+
int getId();
}
@@ -486,8 +502,10 @@
LAUNCHER_TURN_ON_WORK_APPS_TAP(838),
@UiEvent(doc = "User tapped on 'Turn off work apps' button in all apps window.")
- LAUNCHER_TURN_OFF_WORK_APPS_TAP(839)
- ;
+ LAUNCHER_TURN_OFF_WORK_APPS_TAP(839),
+
+ @UiEvent(doc = "Launcher item drop failed since there was not enough room on the screen.")
+ LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE(872);
// ADD MORE
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 4f12d0b..fea15c4 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -16,7 +16,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
+import static com.android.launcher3.WorkspaceLayoutManager.SECOND_SCREEN_ID;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
@@ -297,9 +297,14 @@
int screenCount = workspaceScreens.size();
// First check the preferred screen.
- IntSet screensToExclude = IntSet.wrap(LEFT_PANEL_ID);
+ IntSet screensToExclude = new IntSet();
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
screensToExclude.add(FIRST_SCREEN_ID);
+
+ // On split display we don't want to add the new items onto the second screen.
+ if (app.getInvariantDeviceProfile().isSplitDisplay) {
+ screensToExclude.add(SECOND_SCREEN_ID);
+ }
}
for (int screen = 0; screen < screenCount; screen++) {
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 761053d..1076e88 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -28,6 +28,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.util.IntSet;
import java.util.Locale;
import java.util.Objects;
@@ -44,6 +45,7 @@
public static final int TYPE_PHONE = 0;
public static final int TYPE_MULTI_DISPLAY = 1;
public static final int TYPE_TABLET = 2;
+ public static final IntSet COMPATIBLE_TYPES = IntSet.wrap(TYPE_PHONE, TYPE_MULTI_DISPLAY);
private final String mGridSizeString;
private final int mNumHotseat;
@@ -109,7 +111,9 @@
if (o == null || getClass() != o.getClass()) return false;
DeviceGridState that = (DeviceGridState) o;
return mNumHotseat == that.mNumHotseat
- && mDeviceType == that.mDeviceType
+ && (mDeviceType == that.mDeviceType
+ || (COMPATIBLE_TYPES.contains(mDeviceType)
+ && COMPATIBLE_TYPES.contains(that.mDeviceType)))
&& Objects.equals(mGridSizeString, that.mGridSizeString);
}
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index e64b25c..e7d0749 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -38,7 +38,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
@@ -214,9 +213,6 @@
// Migrate workspace.
// First we create a collection of the screens
List<Integer> screens = new ArrayList<>();
- if (idp.getDeviceProfile(mContext).isTwoPanels) {
- screens.add(Workspace.LEFT_PANEL_ID);
- }
for (int screenId = 0; screenId <= mDestReader.mLastScreenId; screenId++) {
screens.add(screenId);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 41a760b..f4a0eb8 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,7 +16,6 @@
package com.android.launcher3.model;
-import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -179,11 +178,7 @@
// Screen set is never empty
IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
final int firstScreen = allScreens.get(0);
-
IntSet firstScreens = IntSet.wrap(firstScreen);
- if (firstScreen == LEFT_PANEL_ID && allScreens.size() >= 2) {
- firstScreens.add(allScreens.get(1));
- }
filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
new ArrayList<>() /* otherScreenItems are ignored */);
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
deleted file mode 100644
index 756b7da..0000000
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.model;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-
-import java.util.concurrent.Executor;
-
-/**
- * Utility class to preload LauncherModel
- */
-public class ModelPreload implements ModelUpdateTask {
-
- private static final String TAG = "ModelPreload";
-
- private LauncherAppState mApp;
- private LauncherModel mModel;
- private BgDataModel mBgDataModel;
- private AllAppsList mAllAppsList;
-
- @Override
- public final void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
- AllAppsList allAppsList, Executor uiExecutor) {
- mApp = app;
- mModel = model;
- mBgDataModel = dataModel;
- mAllAppsList = allAppsList;
- }
-
- @Override
- public final void run() {
- mModel.startLoaderForResultsIfNotLoaded(
- new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
- MODEL_EXECUTOR.post(() -> {
- Log.d(TAG, "Preload completed : " + mModel.isModelLoaded());
- onComplete(mModel.isModelLoaded());
- });
- }
-
- public BgDataModel getBgDataModel() {
- return mBgDataModel;
- }
-
- /**
- * Called when the task is complete
- */
- @WorkerThread
- public void onComplete(boolean isSuccess) { }
-
- public void start(Context context) {
- LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(this);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 82b0f7c..83fb3d1 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -123,7 +123,6 @@
iconCache.updateIconsForPkg(packages[i], mUser);
activitiesLists.put(
packages[i], appsList.updatePackage(context, packages[i], mUser));
- app.getWidgetCache().removePackage(packages[i], mUser);
// The update may have changed which shortcuts/widgets are available.
// Refresh the widgets for the package if we have an activity running.
@@ -148,7 +147,6 @@
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
appsList.removePackage(packages[i], mUser);
- app.getWidgetCache().removePackage(packages[i], mUser);
}
flagOp = FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 7091d2b..4fdc412 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -232,9 +232,9 @@
* Write the fields of this item to the DB
*/
public void onAddToDatabase(ContentWriter writer) {
- if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
+ if (Workspace.EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
// We should never persist an item on the extra empty screen.
- throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
+ throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
}
writeToValues(writer);
diff --git a/src/com/android/launcher3/notification/NotificationContainer.java b/src/com/android/launcher3/notification/NotificationContainer.java
new file mode 100644
index 0000000..9eb05cd
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationContainer.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.notification;
+
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Class to manage the notification UI in a {@link PopupContainerWithArrow}.
+ *
+ * - Has two {@link NotificationMainView} that represent the top two notifications
+ * - Handles dismissing a notification
+ */
+public class NotificationContainer extends FrameLayout implements SingleAxisSwipeDetector.Listener {
+
+ private static final FloatProperty<NotificationContainer> DRAG_TRANSLATION_X =
+ new FloatProperty<NotificationContainer>("notificationProgress") {
+ @Override
+ public void setValue(NotificationContainer view, float transX) {
+ view.setDragTranslationX(transX);
+ }
+
+ @Override
+ public Float get(NotificationContainer view) {
+ return view.mDragTranslationX;
+ }
+ };
+
+ private static final Rect sTempRect = new Rect();
+
+ private final SingleAxisSwipeDetector mSwipeDetector;
+ private final List<NotificationInfo> mNotificationInfos = new ArrayList<>();
+ private boolean mIgnoreTouch = false;
+
+ private final ObjectAnimator mContentTranslateAnimator;
+ private float mDragTranslationX = 0;
+
+ private final NotificationMainView mPrimaryView;
+ private final NotificationMainView mSecondaryView;
+ private PopupContainerWithArrow mPopupContainer;
+
+ public NotificationContainer(Context context) {
+ this(context, null, 0);
+ }
+
+ public NotificationContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mSwipeDetector = new SingleAxisSwipeDetector(getContext(), this, HORIZONTAL);
+ mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
+ mContentTranslateAnimator = ObjectAnimator.ofFloat(this, DRAG_TRANSLATION_X, 0);
+
+ mPrimaryView = (NotificationMainView) View.inflate(getContext(),
+ R.layout.notification_content, null);
+ mSecondaryView = (NotificationMainView) View.inflate(getContext(),
+ R.layout.notification_content, null);
+ mSecondaryView.setAlpha(0);
+
+ addView(mSecondaryView);
+ addView(mPrimaryView);
+
+ }
+
+ public void setPopupView(PopupContainerWithArrow popupView) {
+ mPopupContainer = popupView;
+ }
+
+ /**
+ * Returns true if we should intercept the swipe.
+ */
+ public boolean onInterceptSwipeEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ sTempRect.set(getLeft(), getTop(), getRight(), getBottom());
+ mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
+ if (!mIgnoreTouch) {
+ mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ if (mIgnoreTouch) {
+ return false;
+ }
+ if (mPrimaryView.getNotificationInfo() == null) {
+ // The notification hasn't been populated yet.
+ return false;
+ }
+
+ mSwipeDetector.onTouchEvent(ev);
+ return mSwipeDetector.isDraggingOrSettling();
+ }
+
+ /**
+ * Returns true when we should handle the swipe.
+ */
+ public boolean onSwipeEvent(MotionEvent ev) {
+ if (mIgnoreTouch) {
+ return false;
+ }
+ if (mPrimaryView.getNotificationInfo() == null) {
+ // The notification hasn't been populated yet.
+ return false;
+ }
+ return mSwipeDetector.onTouchEvent(ev);
+ }
+
+ /**
+ * Applies the list of @param notificationInfos to this container.
+ */
+ public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
+ mNotificationInfos.clear();
+ if (notificationInfos.isEmpty()) {
+ mPrimaryView.applyNotificationInfo(null);
+ mSecondaryView.applyNotificationInfo(null);
+ return;
+ }
+ mNotificationInfos.addAll(notificationInfos);
+
+ NotificationInfo mainNotification = notificationInfos.get(0);
+ mPrimaryView.applyNotificationInfo(mainNotification);
+ mSecondaryView.applyNotificationInfo(notificationInfos.size() > 1
+ ? notificationInfos.get(1)
+ : null);
+ }
+
+ /**
+ * Trims the notifications.
+ * @param notificationKeys List of all valid notification keys.
+ */
+ public void trimNotifications(final List<String> notificationKeys) {
+ Iterator<NotificationInfo> iterator = mNotificationInfos.iterator();
+ while (iterator.hasNext()) {
+ if (!notificationKeys.contains(iterator.next().notificationKey)) {
+ iterator.remove();
+ }
+ }
+
+ NotificationInfo primaryInfo = mNotificationInfos.size() > 0
+ ? mNotificationInfos.get(0)
+ : null;
+ NotificationInfo secondaryInfo = mNotificationInfos.size() > 1
+ ? mNotificationInfos.get(1)
+ : null;
+
+ mPrimaryView.applyNotificationInfo(primaryInfo);
+ mSecondaryView.applyNotificationInfo(secondaryInfo);
+
+ mPrimaryView.onPrimaryDrag(0);
+ mSecondaryView.onSecondaryDrag(0);
+ }
+
+ private void setDragTranslationX(float translationX) {
+ mDragTranslationX = translationX;
+
+ float progress = translationX / getWidth();
+ mPrimaryView.onPrimaryDrag(progress);
+ if (mSecondaryView.getNotificationInfo() == null) {
+ mSecondaryView.setAlpha(0f);
+ } else {
+ mSecondaryView.onSecondaryDrag(progress);
+ }
+ }
+
+ // SingleAxisSwipeDetector.Listener's
+ @Override
+ public void onDragStart(boolean start, float startDisplacement) {
+ mPopupContainer.showArrow(false);
+ }
+
+ @Override
+ public boolean onDrag(float displacement) {
+ if (!mPrimaryView.canChildBeDismissed()) {
+ displacement = OverScroll.dampedScroll(displacement, getWidth());
+ }
+
+ float progress = displacement / getWidth();
+ mPrimaryView.onPrimaryDrag(progress);
+ if (mSecondaryView.getNotificationInfo() == null) {
+ mSecondaryView.setAlpha(0f);
+ } else {
+ mSecondaryView.onSecondaryDrag(progress);
+ }
+ mContentTranslateAnimator.cancel();
+ return true;
+ }
+
+ @Override
+ public void onDragEnd(float velocity) {
+ final boolean willExit;
+ final float endTranslation;
+ final float startTranslation = mPrimaryView.getTranslationX();
+ final float width = getWidth();
+
+ if (!mPrimaryView.canChildBeDismissed()) {
+ willExit = false;
+ endTranslation = 0;
+ } else if (mSwipeDetector.isFling(velocity)) {
+ willExit = true;
+ endTranslation = velocity < 0 ? -width : width;
+ } else if (Math.abs(startTranslation) > width / 2f) {
+ willExit = true;
+ endTranslation = (startTranslation < 0 ? -width : width);
+ } else {
+ willExit = false;
+ endTranslation = 0;
+ }
+
+ long duration = BaseSwipeDetector.calculateDuration(velocity,
+ (endTranslation - startTranslation) / width);
+
+ mContentTranslateAnimator.removeAllListeners();
+ mContentTranslateAnimator.setDuration(duration)
+ .setInterpolator(scrollInterpolatorForVelocity(velocity));
+ mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
+
+ NotificationMainView current = mPrimaryView;
+ mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mSwipeDetector.finishedScrolling();
+ if (willExit) {
+ current.onChildDismissed();
+ }
+ mPopupContainer.showArrow(true);
+ }
+ });
+ mContentTranslateAnimator.start();
+ }
+
+ /**
+ * Animates the background color to a new color.
+ * @param color The color to change to.
+ * @param animatorSetOut The AnimatorSet where we add the color animator to.
+ */
+ public void updateBackgroundColor(int color, AnimatorSet animatorSetOut) {
+ mPrimaryView.updateBackgroundColor(color, animatorSetOut);
+ mSecondaryView.updateBackgroundColor(color, animatorSetOut);
+ }
+
+ /**
+ * Updates the header with a new @param notificationCount.
+ */
+ public void updateHeader(int notificationCount) {
+ mPrimaryView.updateHeader(notificationCount);
+ mSecondaryView.updateHeader(notificationCount - 1);
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
deleted file mode 100644
index af943a6..0000000
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ /dev/null
@@ -1,179 +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.notification;
-
-import android.animation.AnimatorSet;
-import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewOutlineProvider;
-import android.widget.TextView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.util.Themes;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Utility class to manage notification UI
- */
-public class NotificationItemView {
-
- private static final Rect sTempRect = new Rect();
-
- private final Context mContext;
- private final PopupContainerWithArrow mPopupContainer;
- private final ViewGroup mRootView;
-
- private final TextView mHeaderCount;
- private final NotificationMainView mMainView;
-
- private final View mHeader;
-
- private View mGutter;
-
- private boolean mIgnoreTouch = false;
- private List<NotificationInfo> mNotificationInfos = new ArrayList<>();
-
- public NotificationItemView(PopupContainerWithArrow container, ViewGroup rootView) {
- mPopupContainer = container;
- mRootView = rootView;
- mContext = container.getContext();
-
- mHeaderCount = container.findViewById(R.id.notification_count);
- mMainView = container.findViewById(R.id.main_view);
-
- mHeader = container.findViewById(R.id.header);
-
- float radius = Themes.getDialogCornerRadius(mContext);
- rootView.setClipToOutline(true);
- rootView.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
- }
- });
- }
-
- /**
- * Animates the background color to a new color.
- * @param color The color to change to.
- * @param animatorSetOut The AnimatorSet where we add the color animator to.
- */
- public void updateBackgroundColor(int color, AnimatorSet animatorSetOut) {
- mMainView.updateBackgroundColor(color, animatorSetOut);
- }
-
- public void addGutter() {
- if (mGutter == null) {
- mGutter = mPopupContainer.inflateAndAdd(R.layout.notification_gutter, mRootView);
- }
- }
-
- public void inverseGutterMargin() {
- MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
- int top = lp.topMargin;
- lp.topMargin = lp.bottomMargin;
- lp.bottomMargin = top;
- }
-
- public void removeAllViews() {
- mRootView.removeView(mMainView);
- mRootView.removeView(mHeader);
- if (mGutter != null) {
- mRootView.removeView(mGutter);
- }
- }
-
- /**
- * Updates the header text.
- * @param notificationCount The number of notifications.
- */
- public void updateHeader(int notificationCount) {
- final String text;
- final int visibility;
- if (notificationCount <= 1) {
- text = "";
- visibility = View.INVISIBLE;
- } else {
- text = String.valueOf(notificationCount);
- visibility = View.VISIBLE;
-
- }
- mHeaderCount.setText(text);
- mHeaderCount.setVisibility(visibility);
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- sTempRect.set(mRootView.getLeft(), mRootView.getTop(),
- mRootView.getRight(), mRootView.getBottom());
- mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
- if (!mIgnoreTouch) {
- mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true);
- }
- }
- if (mIgnoreTouch) {
- return false;
- }
- if (mMainView.getNotificationInfo() == null) {
- // The notification hasn't been populated yet.
- return false;
- }
-
- return false;
- }
-
- public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
- mNotificationInfos.clear();
- if (notificationInfos.isEmpty()) {
- return;
- }
- mNotificationInfos.addAll(notificationInfos);
-
- NotificationInfo mainNotification = notificationInfos.get(0);
- mMainView.applyNotificationInfo(mainNotification, false);
- }
-
- public void trimNotifications(final List<String> notificationKeys) {
- NotificationInfo currentMainNotificationInfo = mMainView.getNotificationInfo();
- boolean shouldUpdateMainNotification = !notificationKeys.contains(
- currentMainNotificationInfo.notificationKey);
-
- if (shouldUpdateMainNotification) {
- int size = notificationKeys.size();
- NotificationInfo nextNotification = null;
- // We get the latest notification by finding the notification after the one that was
- // just dismissed.
- for (int i = 0; i < size; ++i) {
- if (currentMainNotificationInfo == mNotificationInfos.get(i) && i + 1 < size) {
- nextNotification = mNotificationInfos.get(i + 1);
- break;
- }
- }
- if (nextNotification != null) {
- mMainView.applyNotificationInfo(nextNotification, true);
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b8aa824..f9ff8a6 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -16,62 +16,70 @@
package com.android.launcher3.notification;
+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;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.view.ViewOutlineProvider;
+import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.util.Themes;
/**
* A {@link android.widget.FrameLayout} that contains a single notification,
* e.g. icon + title + text.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class NotificationMainView extends FrameLayout {
-
- private static final FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
- new FloatProperty<NotificationMainView>("contentTranslation") {
- @Override
- public void setValue(NotificationMainView view, float v) {
- view.setContentTranslation(v);
- }
-
- @Override
- public Float get(NotificationMainView view) {
- return view.mTextAndBackground.getTranslationX();
- }
- };
+public class NotificationMainView extends LinearLayout {
// This is used only to track the notification view, so that it can be properly logged.
public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo();
+ // Value when the primary notification main view will be gone (zero alpha).
+ private static final float PRIMARY_GONE_PROGRESS = 0.7f;
+ private static final float PRIMARY_MIN_PROGRESS = 0.40f;
+ private static final float PRIMARY_MAX_PROGRESS = 0.60f;
+ private static final float SECONDARY_MIN_PROGRESS = 0.30f;
+ private static final float SECONDARY_MAX_PROGRESS = 0.50f;
+ private static final float SECONDARY_CONTENT_MAX_PROGRESS = 0.6f;
+
private NotificationInfo mNotificationInfo;
- private ViewGroup mTextAndBackground;
private int mBackgroundColor;
private TextView mTitleView;
private TextView mTextView;
private View mIconView;
- private SingleAxisSwipeDetector mSwipeDetector;
+ private View mHeader;
+ private View mMainView;
- private final ColorDrawable mColorDrawable;
+ private TextView mHeaderCount;
+ private final Rect mOutline = new Rect();
+
+ // Space between notifications during swipe
+ private final int mNotificationSpace;
+ private final int mMaxTransX;
+ private final int mMaxElevation;
+
+ private final GradientDrawable mBackground;
public NotificationMainView(Context context) {
this(context, null, 0);
@@ -82,28 +90,77 @@
}
public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ this(context, attrs, defStyle, 0);
+ }
- mColorDrawable = new ColorDrawable(Color.TRANSPARENT);
+ public NotificationMainView(Context context, AttributeSet attrs, int defStyle, int defStylRes) {
+ super(context, attrs, defStyle, defStylRes);
+
+ float outlineRadius = Themes.getDialogCornerRadius(context);
+
+ mBackground = new GradientDrawable();
+ mBackground.setColor(Themes.getAttrColor(context, R.attr.popupColorPrimary));
+ mBackground.setCornerRadius(outlineRadius);
+ setBackground(mBackground);
+
+ mMaxElevation = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_elevation);
+ setElevation(mMaxElevation);
+
+ mMaxTransX = getResources().getDimensionPixelSize(R.dimen.notification_max_trans);
+ mNotificationSpace = getResources().getDimensionPixelSize(R.dimen.notification_space);
+
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(mOutline, outlineRadius);
+ }
+ });
+ }
+
+ /**
+ * Updates the header text.
+ * @param notificationCount The number of notifications.
+ */
+ public void updateHeader(int notificationCount) {
+ final String text;
+ final int visibility;
+ if (notificationCount <= 1) {
+ text = "";
+ visibility = View.INVISIBLE;
+ } else {
+ text = String.valueOf(notificationCount);
+ visibility = View.VISIBLE;
+
+ }
+ mHeaderCount.setText(text);
+ mHeaderCount.setVisibility(visibility);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mTextAndBackground = findViewById(R.id.text_and_background);
- mTitleView = mTextAndBackground.findViewById(R.id.title);
- mTextView = mTextAndBackground.findViewById(R.id.text);
+ ViewGroup textAndBackground = findViewById(R.id.text_and_background);
+ mTitleView = textAndBackground.findViewById(R.id.title);
+ mTextView = textAndBackground.findViewById(R.id.text);
mIconView = findViewById(R.id.popup_item_icon);
+ mHeaderCount = findViewById(R.id.notification_count);
- ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
- updateBackgroundColor(colorBackground.getColor());
+ mHeader = findViewById(R.id.header);
+ mMainView = findViewById(R.id.main_view);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mOutline.set(0, 0, getWidth(), getHeight());
+ invalidateOutline();
}
private void updateBackgroundColor(int color) {
mBackgroundColor = color;
- mColorDrawable.setColor(color);
- mTextAndBackground.setBackground(mColorDrawable);
+ mBackground.setColor(color);
if (mNotificationInfo != null) {
mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
mBackgroundColor));
@@ -128,8 +185,11 @@
/**
* Sets the content of this view, animating it after a new icon shifts up if necessary.
*/
- public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
- mNotificationInfo = mainNotification;
+ public void applyNotificationInfo(NotificationInfo notificationInfo) {
+ mNotificationInfo = notificationInfo;
+ if (notificationInfo == null) {
+ return;
+ }
NotificationListener listener = NotificationListener.getInstanceIfConnected();
if (listener != null) {
listener.setNotificationsShown(new String[] {mNotificationInfo.notificationKey});
@@ -149,25 +209,112 @@
if (mNotificationInfo.intent != null) {
setOnClickListener(mNotificationInfo);
}
- setContentTranslation(0);
+
// Add a stub ItemInfo so that logging populates the correct container and item types
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
setTag(NOTIFICATION_ITEM_INFO);
- if (animate) {
- ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
+ }
+
+ /**
+ * Sets the alpha of only the child views.
+ */
+ public void setContentAlpha(float alpha) {
+ mHeader.setAlpha(alpha);
+ mMainView.setAlpha(alpha);
+ }
+
+ /**
+ * Sets the translation of only the child views.
+ */
+ public void setContentTranslationX(float transX) {
+ mHeader.setTranslationX(transX);
+ mMainView.setTranslationX(transX);
+ }
+
+ /**
+ * Updates the alpha, content alpha, and elevation of this view.
+ *
+ * @param progress Range from [0, 1] or [-1, 0]
+ * When 0: Full alpha
+ * When 1/-1: zero alpha
+ */
+ public void onPrimaryDrag(float progress) {
+ float absProgress = Math.abs(progress);
+ final int width = getWidth();
+
+ float min = PRIMARY_MIN_PROGRESS;
+ float max = PRIMARY_MAX_PROGRESS;
+
+ if (absProgress < min) {
+ setAlpha(1f);
+ setContentAlpha(1);
+ setElevation(mMaxElevation);
+ } else if (absProgress < max) {
+ setAlpha(1f);
+ setContentAlpha(mapToRange(absProgress, min, max, 1f, 0f, LINEAR));
+ setElevation(Utilities.mapToRange(absProgress, min, max, mMaxElevation, 0, LINEAR));
+ } else {
+ setAlpha(mapToRange(absProgress, max, PRIMARY_GONE_PROGRESS, 1f, 0f, LINEAR));
+ setContentAlpha(0f);
+ setElevation(0f);
}
+
+ setTranslationX(width * progress);
}
- public void setContentTranslation(float translation) {
- mTextAndBackground.setTranslationX(translation);
- mIconView.setTranslationX(translation);
+ /**
+ * Updates the alpha, content alpha, elevation, and clipping of this view.
+ * @param progress Range from [0, 1] or [-1, 0]
+ * When 0: Smallest clipping, zero alpha
+ * When 1/-1: Full clip, full alpha
+ */
+ public void onSecondaryDrag(float progress) {
+ final float absProgress = Math.abs(progress);
+
+ float min = SECONDARY_MIN_PROGRESS;
+ float max = SECONDARY_MAX_PROGRESS;
+ float contentMax = SECONDARY_CONTENT_MAX_PROGRESS;
+
+ if (absProgress < min) {
+ setAlpha(0f);
+ setContentAlpha(0);
+ setElevation(0f);
+ } else if (absProgress < max) {
+ setAlpha(mapToRange(absProgress, min, max, 0, 1f, LINEAR));
+ setContentAlpha(0f);
+ setElevation(0f);
+ } else {
+ setAlpha(1f);
+ setContentAlpha(absProgress > contentMax
+ ? 1f
+ : mapToRange(absProgress, max, contentMax, 0, 1f, LINEAR));
+ setElevation(Utilities.mapToRange(absProgress, max, 1, 0, mMaxElevation, LINEAR));
+ }
+
+ final int width = getWidth();
+ int crop = (int) (width * absProgress);
+ int space = (int) (absProgress > PRIMARY_GONE_PROGRESS
+ ? mapToRange(absProgress, PRIMARY_GONE_PROGRESS, 1f, mNotificationSpace, 0, LINEAR)
+ : mNotificationSpace);
+ if (progress < 0) {
+ mOutline.left = Math.max(0, getWidth() - crop + space);
+ mOutline.right = getWidth();
+ } else {
+ mOutline.right = Math.min(getWidth(), crop - space);
+ mOutline.left = 0;
+ }
+
+ float contentTransX = mMaxTransX * (1f - absProgress);
+ setContentTranslationX(progress < 0
+ ? contentTransX
+ : -contentTransX);
+ invalidateOutline();
}
- public NotificationInfo getNotificationInfo() {
+ public @Nullable NotificationInfo getNotificationInfo() {
return mNotificationInfo;
}
-
public boolean canChildBeDismissed() {
return mNotificationInfo != null && mNotificationInfo.dismissable;
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index f7c730a..29eefe2 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -53,6 +53,9 @@
private static final int ENTER_ANIMATION_STAGGERED_DELAY = 150;
private static final int ENTER_ANIMATION_DURATION = 400;
+ private static final int DOT_ACTIVE_ALPHA = 255;
+ private static final int DOT_INACTIVE_ALPHA = 128;
+
// This value approximately overshoots to 1.5 times the original size.
private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
@@ -75,8 +78,6 @@
private final Paint mCirclePaint;
private final float mDotRadius;
- private final int mActiveColor;
- private final int mInActiveColor;
private final boolean mIsRtl;
private int mNumPages;
@@ -110,12 +111,10 @@
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setStyle(Style.FILL);
+ mCirclePaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
setOutlineProvider(new MyOutlineProver());
- mActiveColor = Themes.getColorAccent(context);
- mInActiveColor = Themes.getAttrColor(context, android.R.attr.colorControlHighlight);
-
mIsRtl = Utilities.isRtl(getResources());
}
@@ -253,18 +252,18 @@
circleGap = -circleGap;
}
for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
- mCirclePaint.setColor(i == mActivePage ? mActiveColor : mInActiveColor);
+ mCirclePaint.setAlpha(i == mActivePage ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], mCirclePaint);
x += circleGap;
}
} else {
- mCirclePaint.setColor(mInActiveColor);
+ mCirclePaint.setAlpha(DOT_INACTIVE_ALPHA);
for (int i = 0; i < mNumPages; i++) {
canvas.drawCircle(x, y, mDotRadius, mCirclePaint);
x += circleGap;
}
- mCirclePaint.setColor(mActiveColor);
+ mCirclePaint.setAlpha(DOT_ACTIVE_ALPHA);
canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mCirclePaint);
}
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index ab35bd6..4b86f65 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -17,7 +17,6 @@
package com.android.launcher3.pm;
import static com.android.launcher3.Utilities.getPrefs;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -25,7 +24,6 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -238,24 +236,12 @@
}
public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) {
- InstallSessionTracker tracker = new InstallSessionTracker(this, callback);
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- mInstaller.registerSessionCallback(tracker, MODEL_EXECUTOR.getHandler());
- } else {
- mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, tracker);
- }
+ InstallSessionTracker tracker = new InstallSessionTracker(
+ this, callback, mInstaller, mLauncherApps);
+ tracker.register();
return tracker;
}
- void unregister(InstallSessionTracker tracker) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- mInstaller.unregisterSessionCallback(tracker);
- } else {
- mLauncherApps.unregisterPackageInstallerSessionCallback(tracker);
- }
- }
-
public static UserHandle getUserHandle(SessionInfo info) {
return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
}
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index b0b907a..e1b3c1a 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -18,9 +18,12 @@
import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
import static com.android.launcher3.pm.PackageInstallInfo.STATUS_FAILED;
import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.Build;
import android.os.UserHandle;
import android.util.SparseArray;
@@ -28,35 +31,53 @@
import com.android.launcher3.util.PackageUserKey;
+import java.lang.ref.WeakReference;
+
@WorkerThread
public class InstallSessionTracker extends PackageInstaller.SessionCallback {
// Lazily initialized
private SparseArray<PackageUserKey> mActiveSessions = null;
- private final InstallSessionHelper mInstallerCompat;
- private final Callback mCallback;
+ private final WeakReference<InstallSessionHelper> mWeakHelper;
+ private final WeakReference<Callback> mWeakCallback;
+ private final PackageInstaller mInstaller;
+ private final LauncherApps mLauncherApps;
- InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback) {
- mInstallerCompat = installerCompat;
- mCallback = callback;
+
+ InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback,
+ PackageInstaller installer, LauncherApps launcherApps) {
+ mWeakHelper = new WeakReference<>(installerCompat);
+ mWeakCallback = new WeakReference<>(callback);
+ mInstaller = installer;
+ mLauncherApps = launcherApps;
}
@Override
public void onCreated(int sessionId) {
- SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+ InstallSessionHelper helper = mWeakHelper.get();
+ Callback callback = mWeakCallback.get();
+ if (callback == null || helper == null) {
+ return;
+ }
+ SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
if (sessionInfo != null) {
- mCallback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
+ callback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
}
- mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+ helper.tryQueuePromiseAppIcon(sessionInfo);
}
@Override
public void onFinished(int sessionId, boolean success) {
+ InstallSessionHelper helper = mWeakHelper.get();
+ Callback callback = mWeakCallback.get();
+ if (callback == null || helper == null) {
+ return;
+ }
// For a finished session, we can't get the session info. So use the
// packageName from our local cache.
- SparseArray<PackageUserKey> activeSessions = getActiveSessionMap();
+ SparseArray<PackageUserKey> activeSessions = getActiveSessionMap(helper);
PackageUserKey key = activeSessions.get(sessionId);
activeSessions.remove(sessionId);
@@ -65,21 +86,26 @@
PackageInstallInfo info = PackageInstallInfo.fromState(
success ? STATUS_INSTALLED : STATUS_FAILED,
packageName, key.mUser);
- mCallback.onPackageStateChanged(info);
+ callback.onPackageStateChanged(info);
- if (!success && mInstallerCompat.promiseIconAddedForId(sessionId)) {
- mCallback.onSessionFailure(packageName, key.mUser);
+ if (!success && helper.promiseIconAddedForId(sessionId)) {
+ callback.onSessionFailure(packageName, key.mUser);
// If it is successful, the id is removed in the the package added flow.
- mInstallerCompat.removePromiseIconId(sessionId);
+ helper.removePromiseIconId(sessionId);
}
}
}
@Override
public void onProgressChanged(int sessionId, float progress) {
- SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+ InstallSessionHelper helper = mWeakHelper.get();
+ Callback callback = mWeakCallback.get();
+ if (callback == null || helper == null) {
+ return;
+ }
+ SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
if (session != null && session.getAppPackageName() != null) {
- mCallback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session));
+ callback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session));
}
}
@@ -88,35 +114,53 @@
@Override
public void onBadgingChanged(int sessionId) {
- SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+ InstallSessionHelper helper = mWeakHelper.get();
+ Callback callback = mWeakCallback.get();
+ if (callback == null || helper == null) {
+ return;
+ }
+ SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
if (sessionInfo != null) {
- mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+ helper.tryQueuePromiseAppIcon(sessionInfo);
}
}
- private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
- SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+ private SessionInfo pushSessionDisplayToLauncher(
+ int sessionId, InstallSessionHelper helper, Callback callback) {
+ SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
if (session != null && session.getAppPackageName() != null) {
PackageUserKey key =
new PackageUserKey(session.getAppPackageName(), getUserHandle(session));
- getActiveSessionMap().put(session.getSessionId(), key);
- mCallback.onUpdateSessionDisplay(key, session);
+ getActiveSessionMap(helper).put(session.getSessionId(), key);
+ callback.onUpdateSessionDisplay(key, session);
return session;
}
return null;
}
- private SparseArray<PackageUserKey> getActiveSessionMap() {
+ private SparseArray<PackageUserKey> getActiveSessionMap(InstallSessionHelper helper) {
if (mActiveSessions == null) {
mActiveSessions = new SparseArray<>();
- mInstallerCompat.getActiveSessions().forEach(
+ helper.getActiveSessions().forEach(
(key, si) -> mActiveSessions.put(si.getSessionId(), key));
}
return mActiveSessions;
}
+ void register() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ mInstaller.registerSessionCallback(this, MODEL_EXECUTOR.getHandler());
+ } else {
+ mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, this);
+ }
+ }
+
public void unregister() {
- mInstallerCompat.unregister(this);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ mInstaller.unregisterSessionCallback(this);
+ } else {
+ mLauncherApps.unregisterPackageInstallerSessionCallback(this);
+ }
}
public interface Callback {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 3bb49f5..112a24e 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -467,6 +467,13 @@
return getMeasuredWidth() - mArrowOffsetHorizontal - mArrowWidth;
}
+ /**
+ * @param show If true, shows arrow (when applicable), otherwise hides arrow.
+ */
+ public void showArrow(boolean show) {
+ mArrow.setVisibility(show && shouldAddArrow() ? VISIBLE : INVISIBLE);
+ }
+
private void addArrow() {
getPopupContainer().addView(mArrow);
mArrow.setX(getX() + getArrowLeft());
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 18f263a..bc3419a 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -58,8 +58,8 @@
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.notification.NotificationContainer;
import com.android.launcher3.notification.NotificationInfo;
-import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -92,9 +92,8 @@
private final int mStartDragThreshold;
private BubbleTextView mOriginalIcon;
- private NotificationItemView mNotificationItemView;
private int mNumNotifications;
- private ViewGroup mNotificationContainer;
+ private NotificationContainer mNotificationContainer;
private ViewGroup mWidgetContainer;
@@ -128,8 +127,8 @@
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mInterceptTouchDown.set(ev.getX(), ev.getY());
}
- if (mNotificationItemView != null
- && mNotificationItemView.onInterceptTouchEvent(ev)) {
+ if (mNotificationContainer != null
+ && mNotificationContainer.onInterceptSwipeEvent(ev)) {
return true;
}
// Stop sending touch events to deep shortcut views if user moved beyond touch slop.
@@ -138,6 +137,14 @@
}
@Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mNotificationContainer != null) {
+ return mNotificationContainer.onSwipeEvent(ev) || super.onTouchEvent(ev);
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
protected boolean isOfType(int type) {
return (type & TYPE_ACTION_POPUP) != 0;
}
@@ -172,8 +179,8 @@
@Override
protected void setChildColor(View view, int color, AnimatorSet animatorSetOut) {
super.setChildColor(view, color, animatorSetOut);
- if (view.getId() == R.id.notification_container && mNotificationItemView != null) {
- mNotificationItemView.updateBackgroundColor(color, animatorSetOut);
+ if (view.getId() == R.id.notification_container && mNotificationContainer != null) {
+ mNotificationContainer.updateBackgroundColor(color, animatorSetOut);
}
}
@@ -232,13 +239,6 @@
mNotificationContainer);
}
- @Override
- protected void onInflationComplete(boolean isReversed) {
- if (isReversed && mNotificationItemView != null) {
- mNotificationItemView.inverseGutterMargin();
- }
- }
-
@TargetApi(Build.VERSION_CODES.P)
public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
@@ -261,9 +261,10 @@
if (mNotificationContainer == null) {
mNotificationContainer = findViewById(R.id.notification_container);
mNotificationContainer.setVisibility(VISIBLE);
+ mNotificationContainer.setPopupView(this);
+ } else {
+ mNotificationContainer.setVisibility(GONE);
}
- View.inflate(getContext(), R.layout.notification_content, mNotificationContainer);
- mNotificationItemView = new NotificationItemView(this, mNotificationContainer);
updateNotificationHeader();
}
int viewsToFlip = getChildCount();
@@ -274,10 +275,6 @@
if (hasDeepShortcuts) {
mDeepShortcutContainer.setVisibility(View.VISIBLE);
- if (mNotificationItemView != null) {
- mNotificationItemView.addGutter();
- }
-
for (int i = shortcutCount; i > 0; i--) {
DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, mDeepShortcutContainer);
v.getLayoutParams().width = containerWidth;
@@ -309,10 +306,6 @@
} else {
mDeepShortcutContainer.setVisibility(View.GONE);
if (!systemShortcuts.isEmpty()) {
- if (mNotificationItemView != null) {
- mNotificationItemView.addGutter();
- }
-
for (SystemShortcut shortcut : systemShortcuts) {
initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
}
@@ -355,13 +348,13 @@
}
public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
- if (mNotificationItemView != null) {
- mNotificationItemView.applyNotificationInfos(notificationInfos);
+ if (mNotificationContainer != null) {
+ mNotificationContainer.applyNotificationInfos(notificationInfos);
}
}
private void updateHiddenShortcuts() {
- int allowedCount = mNotificationItemView != null
+ int allowedCount = mNotificationContainer != null
? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
int total = mShortcuts.size();
@@ -447,8 +440,8 @@
private void updateNotificationHeader() {
ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo);
- if (mNotificationItemView != null && dotInfo != null) {
- mNotificationItemView.updateHeader(dotInfo.getNotificationCount());
+ if (mNotificationContainer != null && dotInfo != null) {
+ mNotificationContainer.updateHeader(dotInfo.getNotificationCount());
}
}
@@ -590,20 +583,18 @@
@Override
public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
- if (mNotificationItemView == null) {
+ if (mNotificationContainer == null) {
return;
}
ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
// No more notifications, remove the notification views and expand all shortcuts.
- mNotificationItemView.removeAllViews();
- mNotificationItemView = null;
mNotificationContainer.setVisibility(GONE);
updateHiddenShortcuts();
assignMarginsAndBackgrounds(PopupContainerWithArrow.this);
} else {
- mNotificationItemView.trimNotifications(
+ mNotificationContainer.trimNotifications(
NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
}
}
diff --git a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
index 5b8d5bc..31436c4 100644
--- a/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
+++ b/src/com/android/launcher3/recyclerview/ViewHolderBinder.java
@@ -17,8 +17,13 @@
import android.view.ViewGroup;
+import androidx.annotation.IntDef;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
/**
* Creates and populates views with data
*
@@ -26,6 +31,15 @@
* @param <V> A subclass of {@link ViewHolder} which holds references to views.
*/
public interface ViewHolderBinder<T, V extends ViewHolder> {
+
+ int POSITION_DEFAULT = 0;
+ int POSITION_FIRST = 1 << 0;
+ int POSITION_LAST = 1 << 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {POSITION_DEFAULT, POSITION_FIRST, POSITION_LAST}, flag = true)
+ @interface ListPosition {}
+
/**
* Creates a new view, and attach it to the parent {@link ViewGroup}. Then, populates UI
* references in a {@link ViewHolder}.
@@ -33,7 +47,7 @@
V newViewHolder(ViewGroup parent);
/** Populate UI references in {@link ViewHolder} with data. */
- void bindViewHolder(V viewHolder, T data, int position);
+ void bindViewHolder(V viewHolder, T data, @ListPosition int position, List<Object> payloads);
/**
* Called when the view is recycled. Views are recycled in batches once they are sufficiently
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 4d63218..b06b8a1 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -20,6 +20,7 @@
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+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;
@@ -29,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
@@ -44,6 +46,7 @@
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -57,12 +60,15 @@
import androidx.preference.SwitchPreference;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.FlagTogglerPrefUi;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.OnboardingPrefs;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -104,6 +110,7 @@
initFlags();
loadPluginPrefs();
maybeAddSandboxCategory();
+ addOnboardingPrefsCatergory();
if (getActivity() != null) {
getActivity().setTitle("Developer Options");
@@ -153,6 +160,15 @@
}
});
+ if (getArguments() != null) {
+ String filter = getArguments().getString(EXTRA_FRAGMENT_ARG_KEY);
+ // Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
+ // key. This is a slight variation where we instead filter by the human-readable titles.
+ if (filter != null) {
+ filterBox.setText(filter);
+ }
+ }
+
View listView = getListView();
final int bottomPadding = listView.getPaddingBottom();
listView.setOnApplyWindowInsetsListener((v, insets) -> {
@@ -355,6 +371,28 @@
sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
}
+ private void addOnboardingPrefsCatergory() {
+ PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
+ onboardingCategory.setSummary("Reset these if you want to see the education again.");
+ for (Map.Entry<String, String[]> titleAndKeys : OnboardingPrefs.ALL_PREF_KEYS.entrySet()) {
+ String title = titleAndKeys.getKey();
+ String[] keys = titleAndKeys.getValue();
+ Preference onboardingPref = new Preference(getContext());
+ onboardingPref.setTitle(title);
+ onboardingPref.setSummary("Tap to reset");
+ onboardingPref.setOnPreferenceClickListener(preference -> {
+ SharedPreferences.Editor sharedPrefsEdit = Utilities.getPrefs(getContext()).edit();
+ for (String key : keys) {
+ sharedPrefsEdit.remove(key);
+ }
+ sharedPrefsEdit.apply();
+ Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
+ return true;
+ });
+ onboardingCategory.addPreference(onboardingPref);
+ }
+ }
+
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "")
.replace("com.android.launcher3.action.PLUGIN_", "");
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 915e140..f348a33 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -62,8 +62,9 @@
SharedPreferences.OnSharedPreferenceChangeListener{
/** List of fragments that can be hosted by this activity. */
- private static final List<String> VALID_PREFERENCE_FRAGMENTS = Collections.singletonList(
- DeveloperOptionsFragment.class.getName());
+ private static final List<String> VALID_PREFERENCE_FRAGMENTS =
+ !Utilities.IS_DEBUG_DEVICE ? Collections.emptyList()
+ : Collections.singletonList(DeveloperOptionsFragment.class.getName());
private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index b34af97..24d3fd4 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -18,6 +18,7 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
@@ -27,12 +28,14 @@
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
+import android.util.Log;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.TestProtocol;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -253,6 +256,9 @@
if (listener != null) {
animation.addListener(listener);
}
+ if (TestProtocol.sDebugTracing && state == NORMAL) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "goToStateAnimated: " + state);
+ }
mUiHandler.post(new StartAnimRunnable(animation));
}
@@ -328,11 +334,17 @@
@Override
public void onAnimationStart(Animator animation) {
// Change the internal state only when the transition actually starts
+ if (TestProtocol.sDebugTracing && state == NORMAL) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "onAnimationStart: " + state);
+ }
onStateTransitionStart(state);
}
@Override
public void onAnimationSuccess(Animator animator) {
+ if (TestProtocol.sDebugTracing && state == NORMAL) {
+ Log.d(TestProtocol.L3_SWIPE_TO_HOME, "onAnimationEnd: " + state);
+ }
onStateTransitionEnd(state);
}
};
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 6f61c0e..86acff7 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -102,14 +102,22 @@
l -> WidgetsFullSheet.getWidgetsView(l).getCurrentScrollY());
}
+ case TestProtocol.REQUEST_TARGET_INSETS: {
+ return getUIProperty(Bundle::putParcelable, activity -> {
+ WindowInsets insets = activity.getWindow()
+ .getDecorView().getRootWindowInsets();
+ return Insets.max(
+ insets.getSystemGestureInsets(),
+ insets.getSystemWindowInsets());
+ }, this::getCurrentActivity);
+ }
+
case TestProtocol.REQUEST_WINDOW_INSETS: {
- return getUIProperty(Bundle::putParcelable, a -> {
- WindowInsets insets = a.getWindow()
+ return getUIProperty(Bundle::putParcelable, activity -> {
+ WindowInsets insets = activity.getWindow()
.getDecorView().getRootWindowInsets();
return Insets.subtract(
- Insets.max(
- insets.getSystemGestureInsets(),
- insets.getSystemWindowInsets()),
+ insets.getSystemWindowInsets(),
Insets.of(0, 0, 0, mDeviceProfile.nonOverlappingTaskbarInset));
}, this::getCurrentActivity);
}
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index 6cdd3ca..103b565 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -17,6 +17,7 @@
package com.android.launcher3.testing;
import android.util.Log;
+import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -48,17 +49,24 @@
}
}
+ private static void registerEventNotFromTest(InputEvent event) {
+ if (!sHadEventsNotFromTest && event.getDeviceId() != -1) {
+ sHadEventsNotFromTest = true;
+ Log.d(TestProtocol.PERMANENT_DIAG_TAG, "First event not from test: " + event);
+ }
+ }
+
public static void recordKeyEvent(String sequence, String message, KeyEvent event) {
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
recordEventSlow(sequence, message + ": " + event);
- if (event.getDeviceId() != -1) sHadEventsNotFromTest = true;
+ registerEventNotFromTest(event);
}
}
public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
if (Utilities.IS_RUNNING_IN_TEST_HARNESS && event.getAction() != MotionEvent.ACTION_MOVE) {
recordEventSlow(sequence, message + ": " + event);
- if (event.getDeviceId() != -1) sHadEventsNotFromTest = true;
+ registerEventNotFromTest(event);
}
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 7d8f479..ed52e20 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -86,6 +86,7 @@
public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y";
+ public static final String REQUEST_TARGET_INSETS = "target-insets";
public static final String REQUEST_WINDOW_INSETS = "window-insets";
public static final String REQUEST_PID = "pid";
public static final String REQUEST_FORCE_GC = "gc";
@@ -116,5 +117,7 @@
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
public static final String WORK_PROFILE_REMOVED = "b/159671700";
public static final String FALLBACK_ACTIVITY_NO_SET = "b/181019015";
- public static final String THIRD_PARTY_LAUNCHER_NOT_SET = "b/187080582";
+ public static final String TASK_VIEW_ID_CRASH = "b/195430732";
+ public static final String L3_SWIPE_TO_HOME = "b/192018189";
+ public static final String NO_DROP_TARGET = "b/195031154";
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 816e5dc..895ca08 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -35,13 +36,16 @@
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.views.BaseDragLayer;
import java.util.Collections;
@@ -162,16 +166,15 @@
}
@Override
- public int getSplitTaskViewDismissDirection(SplitPositionOption splitPosition,
+ public int getSplitTaskViewDismissDirection(@StagePosition int stagePosition,
DeviceProfile dp) {
// Don't use device profile here because we know we're in fake landscape, only split option
// available is top/left
- if (splitPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
// Top (visually left) side
return SPLIT_TRANSLATE_PRIMARY_NEGATIVE;
}
- throw new IllegalStateException("Invalid split stage position: " +
- splitPosition.mStagePosition);
+ throw new IllegalStateException("Invalid split stage position: " + stagePosition);
}
@Override
@@ -364,7 +367,7 @@
@Override
public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
- SplitPositionOption splitPositionOption, Rect out) {
+ @StagePosition int stagePosition, Rect out) {
// In fake land/seascape, the placeholder always needs to go to the "top" of the device,
// which is the same bounds as 0 rotation.
int width = dp.widthPx;
@@ -373,7 +376,7 @@
@Override
public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
+ @StagePosition int stagePosition, Rect out1, Rect out2) {
// In fake land/seascape, the window bounds are always top and bottom half
int screenHeight = dp.heightPx;
int screenWidth = dp.widthPx;
@@ -382,6 +385,53 @@
}
@Override
+ public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
+ SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
+ float diff;
+ float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f;
+ if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+ diff = outRect.height() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff;
+ outRect.bottom -= diff;
+ } else {
+ diff = outRect.height() * splitInfo.leftTaskPercent + horizontalDividerDiff;
+ outRect.top += diff;
+ }
+ }
+
+ @Override
+ public void setLeashSplitOffset(Point splitOffset, DeviceProfile dp,
+ SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
+ if (desiredStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ // The preview set is for the bottom/right, inset by top/left task
+ splitOffset.x = splitInfo.leftTopBounds.width() + splitInfo.visualDividerBounds.width();
+ }
+ }
+
+ @Override
+ public void setGroupedTaskViewThumbnailBounds(View mSnapshotView, View mSnapshotView2,
+ View taskParent, SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
+ DeviceProfile dp) {
+ int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
+ int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
+ int totalThumbnailWidth = taskParent.getWidth();
+ int dividerBar = splitBoundsConfig.visualDividerBounds.width();
+ ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
+ ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
+
+ primaryLp.width = totalThumbnailWidth;
+ primaryLp.height = (int) (totalThumbnailHeight * splitBoundsConfig.leftTaskPercent);
+
+ secondaryLp.width = totalThumbnailWidth;
+ secondaryLp.height = totalThumbnailHeight - primaryLp.height - dividerBar;
+ mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
+ }
+
+ @Override
+ public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
+ throw new IllegalStateException("Default position not available in fake landscape");
+ }
+
+ @Override
public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
DeviceProfile deviceProfile) {
return primary;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index dae2dde..b34a81e 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -19,6 +19,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -31,6 +32,7 @@
import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -78,11 +80,11 @@
FloatProperty<View> getSecondaryViewTranslate();
/**
- * @param splitPosition The position where the view to be split will go
+ * @param stagePosition The position where the view to be split will go
* @return {@link #SPLIT_TRANSLATE_*} constants to indicate which direction the
* dismissal should happen
*/
- int getSplitTaskViewDismissDirection(SplitPositionOption splitPosition, DeviceProfile dp);
+ int getSplitTaskViewDismissDirection(@StagePosition int stagePosition, DeviceProfile dp);
int getPrimaryScroll(View view);
float getPrimaryScale(View view);
int getChildStart(View view);
@@ -120,18 +122,48 @@
* @param splitholderSize height of placeholder view in portrait, width in landscape
*/
void getInitialSplitPlaceholderBounds(int splitholderSize, DeviceProfile dp,
- SplitPositionOption splitPositionOption, Rect out);
+ @StagePosition int stagePosition, Rect out);
/**
* @param splitDividerSize height of split screen drag handle in portrait, width in landscape
- * @param initialSplitOption the split position option (top/left, bottom/right) of the first
+ * @param stagePosition the split position option (top/left, bottom/right) of the first
* task selected for entering split
* @param out1 the bounds for where the first selected app will be
* @param out2 the bounds for where the second selected app will be, complimentary to
* {@param out1} based on {@param initialSplitOption}
*/
void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- SplitPositionOption initialSplitOption, Rect out1, Rect out2);
+ @StagePosition int stagePosition, Rect out1, Rect out2);
+
+ int getDefaultSplitPosition(DeviceProfile deviceProfile);
+
+ /**
+ * @param outRect This is expected to be the rect that has the dimensions for a non-split,
+ * fullscreen task in overview. This will directly be modified.
+ * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
+ * outRect for
+ */
+ void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
+ SplitConfigurationOptions.StagedSplitBounds splitInfo,
+ @SplitConfigurationOptions.StagePosition int desiredStagePosition);
+
+ /**
+ * It's important to note that {@link #setSplitTaskSwipeRect(DeviceProfile, Rect,
+ * SplitConfigurationOptions.StagedSplitBounds, int)} above operates on the outRect based on
+ * launcher's coordinate system, meaning it will treat the outRect as portrait if home rotation
+ * is not allowed.
+ *
+ * However, here the splitOffset is from perspective of TaskViewSimulator, which is in display
+ * orientation coordinates. So, for example, for the fake landscape scenario, even though
+ * launcher is portrait, we inset the bottom/right task by an X coordinate instead of the
+ * usual Y
+ */
+ void setLeashSplitOffset(Point splitOffset, DeviceProfile dp,
+ SplitConfigurationOptions.StagedSplitBounds splitInfo,
+ @SplitConfigurationOptions.StagePosition int desiredStagePosition);
+
+ void setGroupedTaskViewThumbnailBounds(View mSnapshot1, View mSnapshot2, View taskParent,
+ SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp);
// Overview TaskMenuView methods
float getTaskMenuX(float x, View thumbnailView, int overScroll);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 1253589..f1fd439 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -25,6 +25,7 @@
import android.content.res.Resources;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -34,13 +35,16 @@
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
@@ -162,9 +166,9 @@
}
@Override
- public int getSplitTaskViewDismissDirection(SplitPositionOption splitPosition,
+ public int getSplitTaskViewDismissDirection(@StagePosition int stagePosition,
DeviceProfile dp) {
- if (splitPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
if (dp.isLandscape) {
// Left side
return SPLIT_TRANSLATE_PRIMARY_NEGATIVE;
@@ -172,12 +176,11 @@
// Top side
return SPLIT_TRANSLATE_SECONDARY_NEGATIVE;
}
- } else if (splitPosition.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ } else if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
// We don't have a bottom option, so should be right
return SPLIT_TRANSLATE_PRIMARY_POSITIVE;
}
- throw new IllegalStateException("Invalid split stage position: " +
- splitPosition.mStagePosition);
+ throw new IllegalStateException("Invalid split stage position: " + stagePosition);
}
@Override
@@ -413,7 +416,7 @@
@Override
public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
- SplitPositionOption splitPositionOption, Rect out) {
+ @StagePosition int stagePosition, Rect out) {
int width = dp.widthPx;
out.set(0, 0, width, placeholderHeight);
if (!dp.isLandscape) {
@@ -422,7 +425,7 @@
}
// Now we rotate the portrait rect depending on what side we want pinned
- boolean pinToRight = splitPositionOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
+ boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
int screenHeight = dp.heightPx;
float postRotateScale = (float) screenHeight / width;
@@ -439,7 +442,7 @@
@Override
public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
+ @StagePosition int stagePosition, Rect out1, Rect out2) {
int screenHeight = dp.heightPx;
int screenWidth = dp.widthPx;
out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
@@ -450,7 +453,7 @@
}
// Now we rotate the portrait rect depending on what side we want pinned
- boolean pinToRight = initialSplitOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
+ boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
float postRotateScale = (float) screenHeight / screenWidth;
mTmpMatrix.reset();
@@ -468,6 +471,92 @@
}
@Override
+ public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
+ SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
+ boolean isLandscape = dp.isLandscape;
+ float verticalDividerDiff = splitInfo.visualDividerBounds.height() / 2f;
+ float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f;
+ float diff;
+ if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+ if (isLandscape) {
+ diff = outRect.width() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff;
+ outRect.right -= diff;
+ } else {
+ diff = outRect.height() * (1f - splitInfo.topTaskPercent) + verticalDividerDiff;
+ outRect.bottom -= diff;
+ }
+ } else {
+ if (isLandscape) {
+ diff = outRect.width() * splitInfo.leftTaskPercent + horizontalDividerDiff;
+ outRect.left += diff;
+ } else {
+ diff = outRect.height() * splitInfo.topTaskPercent + verticalDividerDiff;
+ outRect.top += diff;
+ }
+ }
+ }
+
+ @Override
+ public void setLeashSplitOffset(Point splitOffset, DeviceProfile dp,
+ SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
+ if (desiredStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+ if (dp.isLandscape) {
+ splitOffset.x = splitInfo.leftTopBounds.width() +
+ splitInfo.visualDividerBounds.width();
+ splitOffset.y = 0;
+ } else {
+ splitOffset.y = splitInfo.leftTopBounds.height() +
+ splitInfo.visualDividerBounds.height();
+ splitOffset.x = 0;
+ }
+ }
+ }
+
+ @Override
+ public void setGroupedTaskViewThumbnailBounds(View mSnapshotView, View mSnapshotView2,
+ View taskParent, SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
+ DeviceProfile dp) {
+ int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
+ int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
+ int totalThumbnailWidth = taskParent.getWidth();
+ int dividerBar = (dp.isLandscape ?
+ splitBoundsConfig.visualDividerBounds.width() :
+ splitBoundsConfig.visualDividerBounds.height());
+ ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
+ ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
+
+ if (dp.isLandscape) {
+ primaryLp.height = totalThumbnailHeight;
+ primaryLp.width = (int) (totalThumbnailWidth * splitBoundsConfig.leftTaskPercent);
+
+ secondaryLp.height = totalThumbnailHeight;
+ secondaryLp.width = totalThumbnailWidth - primaryLp.width - dividerBar;
+ mSnapshotView2.setTranslationX(primaryLp.width + dividerBar);
+ mSnapshotView2.setTranslationY(spaceAboveSnapshot);
+ } else {
+ primaryLp.width = totalThumbnailWidth;
+ primaryLp.height = (int) (totalThumbnailHeight * splitBoundsConfig.topTaskPercent);
+
+ secondaryLp.width = totalThumbnailWidth;
+ secondaryLp.height = totalThumbnailHeight - primaryLp.height - dividerBar;
+ mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
+ mSnapshotView2.setTranslationX(0);
+ }
+ }
+
+ @Override
+ public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
+ if (!deviceProfile.isTablet) {
+ throw new IllegalStateException("Default position available only for large screens");
+ }
+ if (deviceProfile.isLandscape) {
+ return STAGE_POSITION_BOTTOM_OR_RIGHT;
+ } else {
+ return STAGE_POSITION_TOP_OR_LEFT;
+ }
+ }
+
+ @Override
public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
DeviceProfile dp) {
if (dp.isLandscape) { // or seascape
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index e2c0a32..3a2b961 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
+import static java.util.Collections.emptyMap;
+
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ComponentCallbacks;
@@ -34,10 +36,11 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
-import android.view.WindowMetrics;
import androidx.annotation.AnyThread;
import androidx.annotation.UiThread;
@@ -47,7 +50,7 @@
import com.android.launcher3.uioverrides.ApiWrapper;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -55,7 +58,7 @@
* Utility class to cache properties of default display to avoid a system RPC on every call.
*/
@SuppressLint("NewApi")
-public class DisplayController implements DisplayListener, ComponentCallbacks {
+public class DisplayController implements DisplayListener, ComponentCallbacks, SafeCloseable {
private static final String TAG = "DisplayController";
@@ -76,9 +79,10 @@
// Null for SDK < S
private final Context mWindowContext;
-
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+
private Info mInfo;
+ private boolean mDestroyed = false;
private DisplayController(Context context) {
mContext = context;
@@ -95,19 +99,35 @@
mContext.registerReceiver(configChangeReceiver,
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
}
+ mInfo = new Info(getDisplayInfoContext(display), display,
+ getInternalDisplays(mDM), emptyMap());
+ mDM.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+ }
- // Create a single holder for all internal displays. External display holders created
- // lazily.
- Set<PortraitSize> extraInternalDisplays = new ArraySet<>();
- for (Display d : mDM.getDisplays()) {
- if (ApiWrapper.isInternalDisplay(display) && d.getDisplayId() != DEFAULT_DISPLAY) {
+ private static ArrayMap<String, PortraitSize> getInternalDisplays(
+ DisplayManager displayManager) {
+ Display[] displays = displayManager.getDisplays();
+ ArrayMap<String, PortraitSize> internalDisplays = new ArrayMap<>();
+ for (Display display : displays) {
+ if (ApiWrapper.isInternalDisplay(display)) {
Point size = new Point();
- d.getRealSize(size);
- extraInternalDisplays.add(new PortraitSize(size.x, size.y));
+ display.getRealSize(size);
+ internalDisplays.put(ApiWrapper.getUniqueId(display),
+ new PortraitSize(size.x, size.y));
}
}
- mInfo = new Info(getDisplayInfoContext(display), display, extraInternalDisplays);
- mDM.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+ return internalDisplays;
+ }
+
+ @Override
+ public void close() {
+ mDestroyed = true;
+ if (mWindowContext != null) {
+ mWindowContext.unregisterComponentCallbacks(this);
+ } else {
+ // TODO: unregister broadcast receiver
+ }
+ mDM.unregisterDisplayListener(this);
}
@Override
@@ -157,6 +177,9 @@
* Only used for pre-S
*/
private void onConfigChanged(Intent intent) {
+ if (mDestroyed) {
+ return;
+ }
Configuration config = mContext.getResources().getConfiguration();
if (mInfo.fontScale != config.fontScale || mInfo.densityDpi != config.densityDpi) {
Log.d(TAG, "Configuration changed, notifying listeners");
@@ -203,11 +226,16 @@
@AnyThread
private void handleInfoChange(Display display) {
Info oldInfo = mInfo;
- Set<PortraitSize> extraDisplaysSizes = oldInfo.mAllSizes.size() > 1
- ? oldInfo.mAllSizes : Collections.emptySet();
Context displayContext = getDisplayInfoContext(display);
- Info newInfo = new Info(displayContext, display, extraDisplaysSizes);
+ Info newInfo = new Info(displayContext, display,
+ oldInfo.mInternalDisplays, oldInfo.mPerDisplayBounds);
+
+ if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale) {
+ // Cache may not be valid anymore, recreate without cache
+ newInfo = new Info(displayContext, display, getInternalDisplays(mDM), emptyMap());
+ }
+
int change = 0;
if (!newInfo.mScreenSizeDp.equals(oldInfo.mScreenSizeDp)) {
change |= CHANGE_ACTIVE_SCREEN;
@@ -240,7 +268,6 @@
public static class Info {
- public final int id;
public final int singleFrameMs;
// Configuration properties
@@ -249,19 +276,21 @@
public final int densityDpi;
private final PortraitSize mScreenSizeDp;
- private final Set<PortraitSize> mAllSizes;
public final Point currentSize;
public final Set<WindowBounds> supportedBounds = new ArraySet<>();
+ private final Map<String, Set<WindowBounds>> mPerDisplayBounds = new ArrayMap<>();
+ private final ArrayMap<String, PortraitSize> mInternalDisplays;
public Info(Context context, Display display) {
- this(context, display, Collections.emptySet());
+ this(context, display, new ArrayMap<>(), emptyMap());
}
- private Info(Context context, Display display, Set<PortraitSize> extraDisplaysSizes) {
- id = display.getDisplayId();
-
+ private Info(Context context, Display display,
+ ArrayMap<String, PortraitSize> internalDisplays,
+ Map<String, Set<WindowBounds>> perDisplayBoundsCache) {
+ mInternalDisplays = internalDisplays;
rotation = display.getRotation();
Configuration config = context.getResources().getConfiguration();
@@ -271,32 +300,51 @@
singleFrameMs = getSingleFrameMs(display);
currentSize = new Point();
-
display.getRealSize(currentSize);
- if (extraDisplaysSizes.isEmpty() || !Utilities.ATLEAST_S) {
- Point smallestSize = new Point();
- Point largestSize = new Point();
- display.getCurrentSizeRange(smallestSize, largestSize);
+ String myDisplayId = ApiWrapper.getUniqueId(display);
+ Set<WindowBounds> currentSupportedBounds =
+ getSupportedBoundsForDisplay(display, currentSize);
+ mPerDisplayBounds.put(myDisplayId, currentSupportedBounds);
+ supportedBounds.addAll(currentSupportedBounds);
- int portraitWidth = Math.min(currentSize.x, currentSize.y);
- int portraitHeight = Math.max(currentSize.x, currentSize.y);
+ if (ApiWrapper.isInternalDisplay(display) && internalDisplays.size() > 1) {
+ int displayCount = internalDisplays.size();
+ for (int i = 0; i < displayCount; i++) {
+ String displayKey = internalDisplays.keyAt(i);
+ if (TextUtils.equals(myDisplayId, displayKey)) {
+ continue;
+ }
- supportedBounds.add(new WindowBounds(portraitWidth, portraitHeight,
- smallestSize.x, largestSize.y));
- supportedBounds.add(new WindowBounds(portraitHeight, portraitWidth,
- largestSize.x, smallestSize.y));
- mAllSizes = Collections.singleton(new PortraitSize(currentSize.x, currentSize.y));
- } else {
- mAllSizes = new ArraySet<>(extraDisplaysSizes);
- mAllSizes.add(new PortraitSize(currentSize.x, currentSize.y));
- Set<WindowMetrics> metrics = WindowManagerCompat.getDisplayProfiles(
- context, mAllSizes, densityDpi,
- ApiWrapper.TASKBAR_DRAWN_IN_PROCESS);
- metrics.forEach(wm -> supportedBounds.add(WindowBounds.fromWindowMetrics(wm)));
+ Set<WindowBounds> displayBounds = perDisplayBoundsCache.get(displayKey);
+ if (displayBounds == null) {
+ // We assume densityDpi is the same across all internal displays
+ displayBounds = WindowManagerCompat.estimateDisplayProfiles(
+ context, internalDisplays.valueAt(i), densityDpi,
+ ApiWrapper.TASKBAR_DRAWN_IN_PROCESS);
+ }
+
+ supportedBounds.addAll(displayBounds);
+ mPerDisplayBounds.put(displayKey, displayBounds);
+ }
}
}
+ private static Set<WindowBounds> getSupportedBoundsForDisplay(Display display, Point size) {
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+
+ int portraitWidth = Math.min(size.x, size.y);
+ int portraitHeight = Math.max(size.x, size.y);
+ Set<WindowBounds> result = new ArraySet<>();
+ result.add(new WindowBounds(portraitWidth, portraitHeight,
+ smallestSize.x, largestSize.y));
+ result.add(new WindowBounds(portraitHeight, portraitWidth,
+ largestSize.x, smallestSize.y));
+ return result;
+ }
+
/**
* Returns true if the bounds represent a tablet
*/
diff --git a/src/com/android/launcher3/util/HorizontalInsettableView.java b/src/com/android/launcher3/util/HorizontalInsettableView.java
new file mode 100644
index 0000000..7979bc0
--- /dev/null
+++ b/src/com/android/launcher3/util/HorizontalInsettableView.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+/**
+ * Allows the implementing view to add insets to the left and right.
+ */
+public interface HorizontalInsettableView {
+
+ /**
+ * Sets left and right insets for the view so it looks like the width of the view is
+ * reduced when inset is increased.
+ *
+ * The inset is calculated based on the width of the view: e.g. when the width of
+ * the view is 100px then if we apply 0.15f horizontal inset percentage the rendered width
+ * of the view will be 70px with 15px of padding on the left and right sides.
+ *
+ * @param insetPercentage width percentage to inset the content from the left and from the right
+ */
+ void setHorizontalInsets(float insetPercentage);
+
+}
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index e7235e7..1c78795 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -296,7 +296,7 @@
@Override
public void remove() {
- throw new UnsupportedOperationException();
+ removeIndex(--mNextIndex);
}
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index e8ba28f..ab3083d 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -32,6 +32,11 @@
*/
public interface ItemInfoMatcher {
+ /**
+ * Empty component used for match testing
+ */
+ ComponentName EMPTY_COMPONENT = new ComponentName("", "");
+
boolean matches(ItemInfo info, ComponentName cn);
/**
@@ -40,7 +45,7 @@
default boolean matchesInfo(ItemInfo info) {
if (info != null) {
ComponentName cn = info.getTargetComponent();
- return cn != null && matches(info, cn);
+ return matches(info, cn != null ? cn : EMPTY_COMPONENT);
} else {
return false;
}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index f6003dd..badcd35 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -18,13 +18,21 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
+import android.content.ContextWrapper;
import android.os.Looper;
+import android.util.Log;
+import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
/**
@@ -40,8 +48,8 @@
}
public T get(Context context) {
- if (context instanceof PreviewContext) {
- return ((PreviewContext) context).getObject(this, mProvider);
+ if (context instanceof SandboxContext) {
+ return ((SandboxContext) context).getObject(this, mProvider);
}
if (mValue == null) {
@@ -80,4 +88,80 @@
T get(Context context);
}
+
+ /**
+ * Abstract Context which allows custom implementations for
+ * {@link MainThreadInitializedObject} providers
+ */
+ public static abstract class SandboxContext extends ContextWrapper {
+
+ private static final String TAG = "SandboxContext";
+
+ protected final Set<MainThreadInitializedObject> mAllowedObjects;
+ protected final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
+ protected final ArrayList<Object> mOrderedObjects = new ArrayList<>();
+
+ private final Object mDestroyLock = new Object();
+ private boolean mDestroyed = false;
+
+ public SandboxContext(Context base, MainThreadInitializedObject... allowedObjects) {
+ super(base);
+ mAllowedObjects = new HashSet<>(Arrays.asList(allowedObjects));
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ public void onDestroy() {
+ synchronized (mDestroyLock) {
+ // Destroy in reverse order
+ for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
+ Object o = mOrderedObjects.get(i);
+ if (o instanceof SafeCloseable) {
+ ((SafeCloseable) o).close();
+ }
+ }
+ mDestroyed = true;
+ }
+ }
+
+ /**
+ * 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) {
+ synchronized (mDestroyLock) {
+ if (mDestroyed) {
+ Log.e(TAG, "Static object access with a destroyed context");
+ }
+ if (!mAllowedObjects.contains(object)) {
+ throw new IllegalStateException(
+ "Leaking unknown objects " + object + " " + provider);
+ }
+ T t = (T) mObjectMap.get(object);
+ if (t != null) {
+ return t;
+ }
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ t = createObject(provider);
+ mObjectMap.put(object, t);
+ mOrderedObjects.add(t);
+ return t;
+ }
+ }
+
+ try {
+ return MAIN_EXECUTOR.submit(() -> getObject(object, provider)).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @UiThread
+ protected <T> T createObject(ObjectProvider<T> provider) {
+ return provider.get(this);
+ }
+ }
}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 40bc9c3..5ba0d30 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -38,6 +38,15 @@
public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
+ public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
+ // When adding a new key, add it here as well, to be able to reset it from Developer Options.
+ public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
+ "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_EDU_SEEN, SEARCH_SNACKBAR_COUNT },
+ "Taskbar Education", new String[] { TASKBAR_EDU_SEEN }
+ );
/**
* Events that either have happened or have not (booleans).
@@ -45,7 +54,8 @@
@StringDef(value = {
HOME_BOUNCE_SEEN,
HOTSEAT_LONGPRESS_TIP_SEEN,
- SEARCH_EDU_SEEN
+ SEARCH_EDU_SEEN,
+ TASKBAR_EDU_SEEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventBoolKey {
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 10611c7..0c5b722 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -47,7 +47,7 @@
*
* Cache will also be updated if a key queried is missing (even if it has no listeners registered).
*/
-public class SettingsCache extends ContentObserver {
+public class SettingsCache extends ContentObserver implements SafeCloseable {
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
public static final Uri NOTIFICATION_BADGING_URI =
@@ -69,7 +69,6 @@
private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
protected final ContentResolver mResolver;
-
/**
* Singleton instance
*/
@@ -82,6 +81,11 @@
}
@Override
+ public void close() {
+ mResolver.unregisterContentObserver(this);
+ }
+
+ @Override
public void onChange(boolean selfChange, Uri uri) {
// We use default of 1, but if we're getting an onChange call, can assume a non-default
// value will exist
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 573c8bd..5093d85 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -18,6 +18,8 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.graphics.Rect;
+
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
@@ -67,19 +69,56 @@
///////////////////////////////////
public static class SplitPositionOption {
- public final int mIconResId;
- public final int mTextResId;
+ public final int iconResId;
+ public final int textResId;
@StagePosition
- public final int mStagePosition;
+ public final int stagePosition;
@StageType
public final int mStageType;
public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) {
- mIconResId = iconResId;
- mTextResId = textResId;
- mStagePosition = stagePosition;
+ this.iconResId = iconResId;
+ this.textResId = textResId;
+ this.stagePosition = stagePosition;
mStageType = stageType;
}
}
+
+ public static class StagedSplitBounds {
+ public final Rect leftTopBounds;
+ public final Rect rightBottomBounds;
+ /** This rect represents the actual gap between the two apps */
+ public final Rect visualDividerBounds;
+ // This class is orientation-agnostic, so we compute both for later use
+ public final float topTaskPercent;
+ public final float leftTaskPercent;
+
+
+ public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds) {
+ this.leftTopBounds = leftTopBounds;
+ this.rightBottomBounds = rightBottomBounds;
+
+ if (rightBottomBounds.top > leftTopBounds.top) {
+ // vertical apps, horizontal divider
+ this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom,
+ leftTopBounds.right, rightBottomBounds.top);
+ } else {
+ // horizontal apps, vertical divider
+ this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top,
+ rightBottomBounds.left, leftTopBounds.bottom);
+ }
+
+ leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
+ topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+ }
+ }
+
+ public static class StagedSplitTaskPosition {
+ public int taskId = -1;
+ @StagePosition
+ public int stagePosition = STAGE_POSITION_UNDEFINED;
+ @StageType
+ public int stageType = STAGE_TYPE_UNDEFINED;
+ }
}
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index 0f40179..ac5368c 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -28,7 +28,7 @@
import android.view.View;
import android.view.inputmethod.InputMethodManager;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.views.ActivityContext;
/**
@@ -56,7 +56,7 @@
STATS_LOGGER_KEY,
Message.obtain(
HANDLER.get(root.getContext()),
- () -> Launcher.cast(activityContext)
+ () -> BaseActivity.fromContext(root.getContext())
.getStatsLogManager()
.logger()
.log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED)
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index b8554e4..8a7cae9 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -63,31 +63,37 @@
*
* TODO: do different behavior if it's a live wallpaper?
*/
- private void wallpaperOffsetForScroll(int scroll, int numScrollingPages, final int[] out) {
+ private void wallpaperOffsetForScroll(int scroll, int numScrollableScreens, final int[] out) {
out[1] = 1;
// To match the default wallpaper behavior in the system, we default to either the left
// or right edge on initialization
- if (mLockedToDefaultPage || numScrollingPages <= 1) {
+ if (mLockedToDefaultPage || numScrollableScreens <= 1) {
out[0] = mIsRtl ? 1 : 0;
return;
}
// Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace
// screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN)
- int numPagesForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollingPages :
- Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages);
+ int numScreensForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollableScreens :
+ Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollableScreens);
// Offset by the custom screen
- int leftPageIndex;
- int rightPageIndex;
- if (mIsRtl) {
- rightPageIndex = 0;
- leftPageIndex = rightPageIndex + numScrollingPages - 1;
- } else {
- leftPageIndex = 0;
- rightPageIndex = leftPageIndex + numScrollingPages - 1;
- }
+
+ // Don't confuse screens & pages in this function. In a phone UI, we often use screens &
+ // pages interchangeably. However, in a n-panels UI, where n > 1, the screen in this class
+ // means the scrollable screen. Each screen can consist of at most n panels.
+ // Each panel has at most 1 page. Take 5 pages in 2 panels UI as an example, the Workspace
+ // looks as follow:
+ //
+ // S: scrollable screen, P: page, <E>: empty
+ // S0 S1 S2
+ // _______ _______ ________
+ // |P0|P1| |P2|P3| |P4|<E>|
+ // ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯
+ int endIndex = getNumPagesExcludingEmpty() - 1;
+ final int leftPageIndex = mIsRtl ? endIndex : 0;
+ final int rightPageIndex = mIsRtl ? 0 : endIndex;
// Calculate the scroll range
int leftPageScrollX = mWorkspace.getScrollForPage(leftPageIndex);
@@ -103,34 +109,56 @@
int adjustedScroll = scroll - leftPageScrollX -
mWorkspace.getLayoutTransitionOffsetForPage(0);
adjustedScroll = Utilities.boundToRange(adjustedScroll, 0, scrollRange);
- out[1] = (numPagesForWallpaperParallax - 1) * scrollRange;
+ out[1] = (numScreensForWallpaperParallax - 1) * scrollRange;
// The offset is now distributed 0..1 between the left and right pages that we care about,
// so we just map that between the pages that we are using for parallax
int rtlOffset = 0;
if (mIsRtl) {
// In RTL, the pages are right aligned, so adjust the offset from the end
- rtlOffset = out[1] - (numScrollingPages - 1) * scrollRange;
+ rtlOffset = out[1] - (numScrollableScreens - 1) * scrollRange;
}
- out[0] = rtlOffset + adjustedScroll * (numScrollingPages - 1);
+ out[0] = rtlOffset + adjustedScroll * (numScrollableScreens - 1);
}
public float wallpaperOffsetForScroll(int scroll) {
- wallpaperOffsetForScroll(scroll, getNumScreensExcludingEmpty(), sTempInt);
+ wallpaperOffsetForScroll(scroll, getNumScrollableScreensExcludingEmpty(), sTempInt);
return ((float) sTempInt[0]) / sTempInt[1];
}
- private int getNumScreensExcludingEmpty() {
- int numScrollingPages = mWorkspace.getChildCount();
- if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) {
- return numScrollingPages - 1;
+ /**
+ * Returns the number of screens that can be scrolled.
+ *
+ * <p>In an usual phone UI, the number of scrollable screens is equal to the number of
+ * CellLayouts because each screen has exactly 1 CellLayout.
+ *
+ * <p>In a n-panels UI, a screen shows n panels. Each panel has at most 1 CellLayout. Take
+ * 2-panels UI as an example: let's say there are 5 CellLayouts in the Workspace. the number of
+ * scrollable screens will be 3 = ⌈5 / 2⌉.
+ */
+ private int getNumScrollableScreensExcludingEmpty() {
+ float numOfPages = getNumPagesExcludingEmpty();
+ return (int) Math.ceil(numOfPages / mWorkspace.getPanelCount());
+ }
+
+ /**
+ * Returns the number of non-empty pages in the Workspace.
+ *
+ * <p>If a user starts dragging on the rightmost (or leftmost in RTL), an empty CellLayout is
+ * added to the Workspace. This empty CellLayout add as a hover-over target for adding a new
+ * page. To avoid janky motion effect, we ignore this empty CellLayout.
+ */
+ private int getNumPagesExcludingEmpty() {
+ int numOfPages = mWorkspace.getChildCount();
+ if (numOfPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreens()) {
+ return numOfPages - mWorkspace.getPanelCount();
} else {
- return numScrollingPages;
+ return numOfPages;
}
}
public void syncWithScroll() {
- int numScreens = getNumScreensExcludingEmpty();
+ int numScreens = getNumScrollableScreensExcludingEmpty();
wallpaperOffsetForScroll(mWorkspace.getScrollX(), numScreens, sTempInt);
Message msg = Message.obtain(mHandler, MSG_UPDATE_OFFSET, sTempInt[0], sTempInt[1],
mWindowToken);
diff --git a/src/com/android/launcher3/util/WindowManagerCompat.java b/src/com/android/launcher3/util/WindowManagerCompat.java
index 38a63de..bfdf1e4 100644
--- a/src/com/android/launcher3/util/WindowManagerCompat.java
+++ b/src/com/android/launcher3/util/WindowManagerCompat.java
@@ -24,6 +24,7 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Build;
+import android.util.ArraySet;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
@@ -31,14 +32,14 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.DisplayController.PortraitSize;
-import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
/**
- * Utility class to simulate window manager APIs until proper APIs are available
+ * Utility class to estimate window manager values
*/
@TargetApi(Build.VERSION_CODES.S)
public class WindowManagerCompat {
@@ -46,51 +47,51 @@
public static final int MIN_TABLET_WIDTH = 600;
/**
- * Returns a set of supported render sizes for a set of internal displays.
- * This is a temporary workaround which assumes only nav-bar insets change across displays
+ * Returns a set of supported render sizes for a internal display.
+ * This is a temporary workaround which assumes only nav-bar insets change across displays, and
+ * is only used until we eventually get the real values
* @param consumeTaskBar if true, it assumes that task bar is part of the app window
* and ignores any insets because of task bar.
*/
- public static Set<WindowMetrics> getDisplayProfiles(
- Context windowContext, Collection<PortraitSize> allDisplaySizes,
- int densityDpi, boolean consumeTaskBar) {
- WindowInsets metrics = windowContext.getSystemService(WindowManager.class)
+ public static Set<WindowBounds> estimateDisplayProfiles(
+ Context windowContext, PortraitSize size, int densityDpi, boolean consumeTaskBar) {
+ if (!Utilities.ATLEAST_S) {
+ return Collections.emptySet();
+ }
+ WindowInsets defaultInsets = windowContext.getSystemService(WindowManager.class)
.getMaximumWindowMetrics().getWindowInsets();
boolean hasNavbar = ResourceUtils.getIntegerByName(
"config_navBarInteractionMode",
windowContext.getResources(),
INVALID_RESOURCE_HANDLE) != 0;
- WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(metrics);
+ WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(defaultInsets);
+ Set<WindowBounds> result = new ArraySet<>();
+ int swDP = (int) dpiFromPx(size.width, densityDpi);
+ boolean isTablet = swDP >= MIN_TABLET_WIDTH;
- Set<WindowMetrics> result = new HashSet<>();
- for (PortraitSize size : allDisplaySizes) {
- int swDP = (int) dpiFromPx(size.width, densityDpi);
- boolean isTablet = swDP >= MIN_TABLET_WIDTH;
-
- final Insets portraitNav, landscapeNav;
- if (isTablet && !consumeTaskBar) {
- portraitNav = landscapeNav = Insets.of(0, 0, 0, windowContext.getResources()
- .getDimensionPixelSize(R.dimen.taskbar_size));
- } else if (hasNavbar) {
- portraitNav = Insets.of(0, 0, 0,
- getSystemResource(windowContext, "navigation_bar_height", swDP));
- landscapeNav = isTablet
- ? Insets.of(0, 0, 0, getSystemResource(windowContext,
- "navigation_bar_height_landscape", swDP))
- : Insets.of(0, 0, getSystemResource(windowContext,
- "navigation_bar_width", swDP), 0);
- } else {
- portraitNav = landscapeNav = Insets.of(0, 0, 0, 0);
- }
-
- result.add(new WindowMetrics(
- new Rect(0, 0, size.width, size.height),
- insetsBuilder.setInsets(Type.navigationBars(), portraitNav).build()));
- result.add(new WindowMetrics(
- new Rect(0, 0, size.height, size.width),
- insetsBuilder.setInsets(Type.navigationBars(), landscapeNav).build()));
+ final Insets portraitNav, landscapeNav;
+ if (isTablet && !consumeTaskBar) {
+ portraitNav = landscapeNav = Insets.of(0, 0, 0, windowContext.getResources()
+ .getDimensionPixelSize(R.dimen.taskbar_size));
+ } else if (hasNavbar) {
+ portraitNav = Insets.of(0, 0, 0,
+ getSystemResource(windowContext, "navigation_bar_height", swDP));
+ landscapeNav = isTablet
+ ? Insets.of(0, 0, 0, getSystemResource(windowContext,
+ "navigation_bar_height_landscape", swDP))
+ : Insets.of(0, 0, getSystemResource(windowContext,
+ "navigation_bar_width", swDP), 0);
+ } else {
+ portraitNav = landscapeNav = Insets.of(0, 0, 0, 0);
}
+
+ result.add(WindowBounds.fromWindowMetrics(new WindowMetrics(
+ new Rect(0, 0, size.width, size.height),
+ insetsBuilder.setInsets(Type.navigationBars(), portraitNav).build())));
+ result.add(WindowBounds.fromWindowMetrics(new WindowMetrics(
+ new Rect(0, 0, size.height, size.width),
+ insetsBuilder.setInsets(Type.navigationBars(), landscapeNav).build())));
return result;
}
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 92ca8a1..8ac40b8 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -28,6 +28,7 @@
import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
@@ -68,7 +69,7 @@
protected final SingleAxisSwipeDetector mSwipeDetector;
protected final ObjectAnimator mOpenCloseAnimator;
- protected View mContent;
+ protected ViewGroup mContent;
protected final View mColorScrim;
protected Interpolator mScrollInterpolator;
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index b95904e..dc5fe06 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -27,6 +27,7 @@
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ViewCache;
@@ -109,6 +110,17 @@
return null;
}
+ default StatsLogManager getStatsLogManager() {
+ return StatsLogManager.newInstance((Context) this);
+ }
+
+ /**
+ * Returns whether we can show the IME for elements hosted by this ActivityContext.
+ */
+ default boolean supportsIme() {
+ return true;
+ }
+
/**
* Returns the ActivityContext associated with the given Context.
*/
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 1c2534d..2b0f707 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -40,7 +40,6 @@
import android.view.WindowInsets;
import android.widget.TextView;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.recyclerview.widget.RecyclerView;
@@ -131,7 +130,6 @@
protected BaseRecyclerView mRv;
private RecyclerView.OnScrollListener mOnScrollListener;
- @Nullable private OnFastScrollChangeListener mOnFastScrollChangeListener;
private int mDownX;
private int mDownY;
@@ -208,7 +206,6 @@
int rvCurrentOffsetY = mRv.getCurrentScrollY();
if (mRvOffsetY != rvCurrentOffsetY) {
mRvOffsetY = mRv.getCurrentScrollY();
- notifyScrollChanged();
}
return;
}
@@ -216,7 +213,6 @@
mThumbOffsetY = y;
invalidate();
mRvOffsetY = mRv.getCurrentScrollY();
- notifyScrollChanged();
}
public int getThumbOffsetY() {
@@ -461,23 +457,4 @@
public void setIsRecyclerViewFirstChildInParent(boolean isRecyclerViewFirstChildInParent) {
mIsRecyclerViewFirstChildInParent = isRecyclerViewFirstChildInParent;
}
-
- public void setOnFastScrollChangeListener(
- @Nullable OnFastScrollChangeListener onFastScrollChangeListener) {
- mOnFastScrollChangeListener = onFastScrollChangeListener;
- }
-
- private void notifyScrollChanged() {
- if (mOnFastScrollChangeListener != null) {
- mOnFastScrollChangeListener.onScrollChanged();
- }
- }
-
- /**
- * A callback that is invoked when there is a scroll change in {@link RecyclerViewFastScroller}.
- */
- public interface OnFastScrollChangeListener {
- /** Called when the recycler view scroll has changed. */
- void onScrollChanged();
- }
}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 1eb79ad..4c0bfde 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -25,23 +25,27 @@
import android.util.AttributeSet;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.Insettable;
import com.android.launcher3.util.SystemUiController;
+import java.util.ArrayList;
+
/**
* Simple scrim which draws a flat color
*/
public class ScrimView extends View implements Insettable {
private static final float STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.9f;
+ private final ArrayList<Runnable> mOpaquenessListeners = new ArrayList<>(1);
private SystemUiController mSystemUiController;
-
private ScrimDrawingController mDrawingController;
private int mBackgroundColor;
private boolean mIsVisible = true;
+ private boolean mLastDispatchedOpaqueness;
public ScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -60,6 +64,7 @@
@Override
protected boolean onSetAlpha(int alpha) {
updateSysUiColors();
+ dispatchVisibilityListenersIfNeeded();
return super.onSetAlpha(alpha);
}
@@ -67,6 +72,7 @@
public void setBackgroundColor(int color) {
mBackgroundColor = color;
updateSysUiColors();
+ dispatchVisibilityListenersIfNeeded();
super.setBackgroundColor(color);
}
@@ -74,6 +80,7 @@
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
mIsVisible = isVisible;
+ dispatchVisibilityListenersIfNeeded();
}
public boolean isFullyOpaque() {
@@ -108,6 +115,17 @@
}
}
+ private void dispatchVisibilityListenersIfNeeded() {
+ boolean fullyOpaque = isFullyOpaque();
+ if (mLastDispatchedOpaqueness == fullyOpaque) {
+ return;
+ }
+ mLastDispatchedOpaqueness = fullyOpaque;
+ for (int i = 0; i < mOpaquenessListeners.size(); i++) {
+ mOpaquenessListeners.get(i).run();
+ }
+ }
+
private SystemUiController getSystemUiController() {
if (mSystemUiController == null) {
mSystemUiController = BaseActivity.fromContext(getContext()).getSystemUiController();
@@ -136,6 +154,22 @@
}
/**
+ * Registers a listener to be notified of whether the scrim is occluding other UI elements.
+ * @see #isFullyOpaque()
+ */
+ public void addOpaquenessListener(@NonNull Runnable listener) {
+ mOpaquenessListeners.add(listener);
+ }
+
+ /**
+ * Removes previously registered listener.
+ * @see #addOpaquenessListener(Runnable)
+ */
+ public void removeOpaquenessListener(@NonNull Runnable listener) {
+ mOpaquenessListeners.remove(listener);
+ }
+
+ /**
* A Utility interface allowing for other surfaces to draw on ScrimView
*/
public interface ScrimDrawingController {
diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java
deleted file mode 100644
index 92cce92..0000000
--- a/src/com/android/launcher3/views/TopRoundedCornerView.java
+++ /dev/null
@@ -1,61 +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.views;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-
-import com.android.launcher3.util.Themes;
-
-/**
- * View with top rounded corners.
- */
-public class TopRoundedCornerView extends SpringRelativeLayout {
-
- private final RectF mRect = new RectF();
- private final Path mClipPath = new Path();
- private float[] mRadii;
-
- public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- float radius = Themes.getDialogCornerRadius(context);
- mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0};
- }
-
- public TopRoundedCornerView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.save();
- canvas.clipPath(mClipPath);
- super.draw(canvas);
- canvas.restore();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- mClipPath.reset();
- mClipPath.addRoundRect(mRect, mRadii, Path.Direction.CW);
- }
-}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index b6cec12..00a0050 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -19,6 +19,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
@@ -108,6 +109,9 @@
@Override
public boolean onLongClick(View v) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "1");
+ }
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
v.cancelLongPress();
if (!ItemLongClickListener.canStartDrag(mActivityContext)) return false;
@@ -178,6 +182,9 @@
}
private boolean beginDraggingWidget(WidgetCell v) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "2");
+ }
// Get the widget preview as the drag representation
WidgetImageView image = v.getWidgetView();
diff --git a/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java b/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java
deleted file mode 100644
index afceadd..0000000
--- a/src/com/android/launcher3/widget/CachingWidgetPreviewLoader.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.collection.ArrayMap;
-import androidx.collection.ArraySet;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.ComponentKey;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/** Wrapper around {@link DatabaseWidgetPreviewLoader} that contains caching logic. */
-public class CachingWidgetPreviewLoader implements WidgetPreviewLoader {
-
- @NonNull private final WidgetPreviewLoader mDelegate;
- @NonNull private final Map<ComponentKey, Map<Size, CacheResult>> mCache = new ArrayMap<>();
-
- public CachingWidgetPreviewLoader(@NonNull WidgetPreviewLoader delegate) {
- mDelegate = delegate;
- }
-
- /** Returns whether the preview is loaded for the item and size. */
- public boolean isPreviewLoaded(@NonNull WidgetItem item, @NonNull Size previewSize) {
- return getPreview(item, previewSize) != null;
- }
-
- /** Returns the cached preview for the item and size, or null if there is none. */
- @Nullable
- public Bitmap getPreview(@NonNull WidgetItem item, @NonNull Size previewSize) {
- CacheResult cacheResult = getCacheResult(item, previewSize);
- if (cacheResult instanceof CacheResult.Loaded) {
- return ((CacheResult.Loaded) cacheResult).mBitmap;
- } else {
- return null;
- }
- }
-
- @NonNull
- private CacheResult getCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
- synchronized (mCache) {
- Map<Size, CacheResult> cacheResults = mCache.get(toComponentKey(item));
- if (cacheResults == null) {
- return CacheResult.MISS;
- }
-
- return cacheResults.getOrDefault(previewSize, CacheResult.MISS);
- }
- }
-
- /**
- * Puts the result in the cache for the item and size. Returns the value previously in the
- * cache, or null if there was none.
- */
- @Nullable
- private CacheResult putCacheResult(
- @NonNull WidgetItem item,
- @NonNull Size previewSize,
- @Nullable CacheResult cacheResult) {
- ComponentKey key = toComponentKey(item);
- synchronized (mCache) {
- Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
- CacheResult previous;
- if (cacheResult == null) {
- previous = cacheResults.remove(previewSize);
- if (cacheResults.isEmpty()) {
- mCache.remove(key);
- } else {
- previous = cacheResults.put(previewSize, cacheResult);
- mCache.put(key, cacheResults);
- }
- } else {
- previous = cacheResults.put(previewSize, cacheResult);
- mCache.put(key, cacheResults);
- }
- return previous;
- }
- }
-
- private void removeCacheResult(@NonNull WidgetItem item, @NonNull Size previewSize) {
- ComponentKey key = toComponentKey(item);
- synchronized (mCache) {
- Map<Size, CacheResult> cacheResults = mCache.getOrDefault(key, new ArrayMap<>());
- cacheResults.remove(previewSize);
- mCache.put(key, cacheResults);
- }
- }
-
- /**
- * Gets the preview for the widget item and size, using the value in the cache if stored.
- *
- * @return a {@link CancellationSignal}, which can cancel the request before it loads
- */
- @Override
- @UiThread
- @NonNull
- public CancellationSignal loadPreview(
- @NonNull BaseActivity activity, @NonNull WidgetItem item, @NonNull Size previewSize,
- @NonNull WidgetPreviewLoadedCallback callback) {
- CancellationSignal signal = new CancellationSignal();
- signal.setOnCancelListener(() -> {
- synchronized (mCache) {
- CacheResult cacheResult = getCacheResult(item, previewSize);
- if (!(cacheResult instanceof CacheResult.Loading)) {
- // If the key isn't actively loading, then this is a no-op. Cancelling loading
- // shouldn't clear the cache if we've already loaded.
- return;
- }
-
- CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
- CacheResult.Loading updated = prev.withoutCallback(callback);
-
- if (updated.mCallbacks.isEmpty()) {
- // If the last callback was removed, then cancel the underlying request in the
- // delegate.
- prev.mCancellationSignal.cancel();
- removeCacheResult(item, previewSize);
- } else {
- // If there are other callbacks still active, then don't cancel the delegate's
- // request, just remove this callback from the set.
- putCacheResult(item, previewSize, updated);
- }
- }
- });
-
- synchronized (mCache) {
- CacheResult cacheResult = getCacheResult(item, previewSize);
- if (cacheResult instanceof CacheResult.Loaded) {
- // If the bitmap is already present in the cache, invoke the callback immediately.
- callback.onPreviewLoaded(((CacheResult.Loaded) cacheResult).mBitmap);
- return signal;
- }
-
- if (cacheResult instanceof CacheResult.Loading) {
- // If we're already loading the preview for this key, then just add the callback
- // to the set we'll call after it loads.
- CacheResult.Loading prev = (CacheResult.Loading) cacheResult;
- putCacheResult(item, previewSize, prev.withCallback(callback));
- return signal;
- }
-
- CancellationSignal delegateCancellationSignal =
- mDelegate.loadPreview(
- activity,
- item,
- previewSize,
- preview -> {
- CacheResult prev;
- synchronized (mCache) {
- prev = putCacheResult(
- item, previewSize, new CacheResult.Loaded(preview));
- }
- if (prev instanceof CacheResult.Loading) {
- // Notify each stored callback that the preview has loaded.
- ((CacheResult.Loading) prev).mCallbacks
- .forEach(c -> c.onPreviewLoaded(preview));
- } else {
- // If there isn't a loading object in the cache, then we were
- // notified before adding this signal to the cache. Just
- // call back to the provided callback, there can't be others.
- callback.onPreviewLoaded(preview);
- }
- });
- ArraySet<WidgetPreviewLoadedCallback> callbacks = new ArraySet<>();
- callbacks.add(callback);
- putCacheResult(
- item,
- previewSize,
- new CacheResult.Loading(delegateCancellationSignal, callbacks));
- }
-
- return signal;
- }
-
- /** Clears all cached previews for {@code items}, cancelling any in-progress preview loading. */
- public void clearPreviews(Iterable<WidgetItem> items) {
- List<CacheResult> previousCacheResults = new ArrayList<>();
- synchronized (mCache) {
- for (WidgetItem item : items) {
- Map<Size, CacheResult> previousMap = mCache.remove(toComponentKey(item));
- if (previousMap != null) {
- previousCacheResults.addAll(previousMap.values());
- }
- }
- }
-
- for (CacheResult previousCacheResult : previousCacheResults) {
- if (previousCacheResult instanceof CacheResult.Loading) {
- ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
- }
- }
- }
-
- /** Clears all cached previews, cancelling any in-progress preview loading. */
- public void clearAll() {
- List<CacheResult> previousCacheResults;
- synchronized (mCache) {
- previousCacheResults =
- mCache
- .values()
- .stream()
- .flatMap(sizeToResult -> sizeToResult.values().stream())
- .collect(Collectors.toList());
- mCache.clear();
- }
-
- for (CacheResult previousCacheResult : previousCacheResults) {
- if (previousCacheResult instanceof CacheResult.Loading) {
- ((CacheResult.Loading) previousCacheResult).mCancellationSignal.cancel();
- }
- }
- }
-
- private abstract static class CacheResult {
- static final CacheResult MISS = new CacheResult() {};
-
- static final class Loading extends CacheResult {
- @NonNull final CancellationSignal mCancellationSignal;
- @NonNull final Set<WidgetPreviewLoadedCallback> mCallbacks;
-
- Loading(@NonNull CancellationSignal cancellationSignal,
- @NonNull Set<WidgetPreviewLoadedCallback> callbacks) {
- mCancellationSignal = cancellationSignal;
- mCallbacks = callbacks;
- }
-
- @NonNull
- Loading withCallback(@NonNull WidgetPreviewLoadedCallback callback) {
- if (mCallbacks.contains(callback)) return this;
- Set<WidgetPreviewLoadedCallback> newCallbacks =
- new ArraySet<>(mCallbacks.size() + 1);
- newCallbacks.addAll(mCallbacks);
- newCallbacks.add(callback);
- return new Loading(mCancellationSignal, newCallbacks);
- }
-
- @NonNull
- Loading withoutCallback(@NonNull WidgetPreviewLoadedCallback callback) {
- if (!mCallbacks.contains(callback)) return this;
- Set<WidgetPreviewLoadedCallback> newCallbacks =
- new ArraySet<>(mCallbacks.size() - 1);
- for (WidgetPreviewLoadedCallback existingCallback : mCallbacks) {
- if (!existingCallback.equals(callback)) {
- newCallbacks.add(existingCallback);
- }
- }
- return new Loading(mCancellationSignal, newCallbacks);
- }
- }
-
- static final class Loaded extends CacheResult {
- @NonNull final Bitmap mBitmap;
-
- Loaded(@NonNull Bitmap bitmap) {
- mBitmap = bitmap;
- }
- }
- }
-
- @NonNull
- private static ComponentKey toComponentKey(@NonNull WidgetItem item) {
- return new ComponentKey(item.componentName, item.user);
- }
-}
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index 4ec7e60..aacb9c5 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -16,21 +16,10 @@
package com.android.launcher3.widget;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import android.content.ComponentName;
-import android.content.ContentValues;
import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -39,72 +28,40 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.CancellationSignal;
+import android.os.Handler;
import android.os.Process;
-import android.os.UserHandle;
import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.Pair;
import android.util.Size;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShadowGenerator;
+import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.SQLiteCacheHelper;
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.util.WidgetSizes;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.WeakHashMap;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
-/** {@link WidgetPreviewLoader} that loads preview images from a {@link CacheDb}. */
-public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader {
+/** Utility class to load widget previews */
+public class DatabaseWidgetPreviewLoader {
private static final String TAG = "WidgetPreviewLoader";
- private static final boolean DEBUG = false;
-
- private final HashMap<String, long[]> mPackageVersions = new HashMap<>();
-
- /**
- * Weak reference objects, do not prevent their referents from being made finalizable,
- * finalized, and then reclaimed.
- * Note: synchronized block used for this variable is expensive and the block should always
- * be posted to a background thread.
- */
- @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>());
private final Context mContext;
- private final IconCache mIconCache;
- private final UserCache mUserCache;
- private final CacheDb mDb;
private final float mPreviewBoxCornerRadius;
- public DatabaseWidgetPreviewLoader(Context context, IconCache iconCache) {
+ public DatabaseWidgetPreviewLoader(Context context) {
mContext = context;
- mIconCache = iconCache;
- mUserCache = UserCache.INSTANCE.get(context);
- mDb = new CacheDb(context);
float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
mPreviewBoxCornerRadius = previewCornerRadius > 0
? previewCornerRadius
@@ -117,251 +74,29 @@
*
* @return a request id which can be used to cancel the request.
*/
- @Override
@NonNull
- public CancellationSignal loadPreview(
- @NonNull BaseActivity activity,
+ public HandlerRunnable loadPreview(
@NonNull WidgetItem item,
@NonNull Size previewSize,
- @NonNull WidgetPreviewLoadedCallback callback) {
- int previewWidth = previewSize.getWidth();
- int previewHeight = previewSize.getHeight();
- String size = previewWidth + "x" + previewHeight;
- WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
-
- PreviewLoadTask task =
- new PreviewLoadTask(activity, key, item, previewWidth, previewHeight, callback);
- task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
-
- CancellationSignal signal = new CancellationSignal();
- signal.setOnCancelListener(task);
- return signal;
- }
-
- /** Clears the database storing previews. */
- public void refresh() {
- mDb.clear();
- }
-
- /**
- * The DB holds the generated previews for various components. Previews can also have different
- * sizes (landscape vs portrait).
- */
- private static class CacheDb extends SQLiteCacheHelper {
- private static final int DB_VERSION = 9;
-
- private static final String TABLE_NAME = "shortcut_and_widget_previews";
- private static final String COLUMN_COMPONENT = "componentName";
- private static final String COLUMN_USER = "profileId";
- private static final String COLUMN_SIZE = "size";
- private static final String COLUMN_PACKAGE = "packageName";
- private static final String COLUMN_LAST_UPDATED = "lastUpdated";
- private static final String COLUMN_VERSION = "version";
- private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
-
- CacheDb(Context context) {
- super(context, LauncherFiles.WIDGET_PREVIEWS_DB, DB_VERSION, TABLE_NAME);
- }
-
- @Override
- public void onCreateTable(SQLiteDatabase database) {
- database.execSQL("CREATE TABLE IF NOT EXISTS "
- + TABLE_NAME
- + " ("
- + COLUMN_COMPONENT
- + " TEXT NOT NULL, "
- + COLUMN_USER
- + " INTEGER NOT NULL, "
- + COLUMN_SIZE
- + " TEXT NOT NULL, "
- + COLUMN_PACKAGE
- + " TEXT NOT NULL, "
- + COLUMN_LAST_UPDATED
- + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_VERSION
- + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_PREVIEW_BITMAP
- + " BLOB, "
- + "PRIMARY KEY ("
- + COLUMN_COMPONENT
- + ", "
- + COLUMN_USER
- + ", "
- + COLUMN_SIZE
- + ") "
- +
- ");");
- }
- }
-
- @Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) {
- ContentValues values = new ContentValues();
- values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString());
- values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user));
- values.put(CacheDb.COLUMN_SIZE, key.mSize);
- values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName());
- values.put(CacheDb.COLUMN_VERSION, versions[0]);
- values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]);
- values.put(CacheDb.COLUMN_PREVIEW_BITMAP, GraphicsUtils.flattenBitmap(preview));
- mDb.insertOrReplace(values);
- }
-
- /** Removes the package from the preview database. */
- public void removePackage(String packageName, UserHandle user) {
- removePackage(packageName, user, mUserCache.getSerialNumberForUser(user));
- }
-
- /** Removes the package from the preview database. */
- public void removePackage(String packageName, UserHandle user, long userSerial) {
- synchronized (mPackageVersions) {
- mPackageVersions.remove(packageName);
- }
-
- mDb.delete(
- CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?",
- new String[]{packageName, Long.toString(userSerial)});
- }
-
- /**
- * Updates the persistent DB:
- * 1. Any preview generated for an old package version is removed
- * 2. Any preview for an absent package is removed
- * This ensures that we remove entries for packages which changed while the launcher was dead.
- *
- * @param packageUser if provided, specifies that list only contains previews for the
- * given package/user, otherwise the list contains all previews
- */
- public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list,
- @Nullable PackageUserKey packageUser) {
- Preconditions.assertWorkerThread();
-
- LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
-
- for (ComponentKey key : list) {
- final long userId = mUserCache.getSerialNumberForUser(key.user);
- HashSet<String> packages = validPackages.get(userId);
- if (packages == null) {
- packages = new HashSet<>();
- validPackages.put(userId, packages);
- }
- packages.add(key.componentName.getPackageName());
- }
-
- LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>();
- long passedUserId = packageUser == null ? 0
- : mUserCache.getSerialNumberForUser(packageUser.mUser);
- Cursor c = null;
- try {
- c = mDb.query(
- new String[]{CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE,
- CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION},
- null, null);
- while (c.moveToNext()) {
- long userId = c.getLong(0);
- String pkg = c.getString(1);
- long lastUpdated = c.getLong(2);
- long version = c.getLong(3);
-
- if (packageUser != null && (!pkg.equals(packageUser.mPackageName)
- || userId != passedUserId)) {
- // This preview is associated with a different package/user, no need to remove.
- continue;
- }
-
- HashSet<String> packages = validPackages.get(userId);
- if (packages != null && packages.contains(pkg)) {
- long[] versions = getPackageVersion(pkg);
- if (versions[0] == version && versions[1] == lastUpdated) {
- // Every thing checks out
- continue;
- }
- }
-
- // We need to delete this package.
- packages = packagesToDelete.get(userId);
- if (packages == null) {
- packages = new HashSet<>();
- packagesToDelete.put(userId, packages);
- }
- packages.add(pkg);
- }
-
- for (int i = 0; i < packagesToDelete.size(); i++) {
- long userId = packagesToDelete.keyAt(i);
- UserHandle user = mUserCache.getUserForSerialNumber(userId);
- for (String pkg : packagesToDelete.valueAt(i)) {
- removePackage(pkg, user, userId);
- }
- }
- } catch (SQLException e) {
- Log.e(TAG, "Error updating widget previews", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- /**
- * Reads the preview bitmap from the DB or null if the preview is not in the DB.
- */
- @Thunk Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle, PreviewLoadTask loadTask) {
- Cursor cursor = null;
- try {
- cursor = mDb.query(
- new String[]{CacheDb.COLUMN_PREVIEW_BITMAP},
- CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND "
- + CacheDb.COLUMN_SIZE + " = ?",
- new String[]{
- key.componentName.flattenToShortString(),
- Long.toString(mUserCache.getSerialNumberForUser(key.user)),
- key.mSize
- });
- // If cancelled, skip getting the blob and decoding it into a bitmap
- if (loadTask.isCancelled()) {
- return null;
- }
- if (cursor.moveToNext()) {
- byte[] blob = cursor.getBlob(0);
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inBitmap = recycle;
- try {
- if (!loadTask.isCancelled()) {
- return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
- }
- } catch (Exception e) {
- return null;
- }
- }
- } catch (SQLException e) {
- Log.w(TAG, "Error loading preview from DB", e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return null;
+ @NonNull Consumer<Bitmap> callback) {
+ Handler handler = Executors.UI_HELPER_EXECUTOR.getHandler();
+ HandlerRunnable<Bitmap> request = new HandlerRunnable<>(handler,
+ () -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()),
+ MAIN_EXECUTOR,
+ callback);
+ Utilities.postAsyncCallback(handler, request);
+ return request;
}
/**
* Returns a generated preview for a widget and if the preview should be saved in persistent
* storage.
- * @param launcher
- * @param item
- * @param recycle
- * @param previewWidth
- * @param previewHeight
- * @return Pair<Bitmap, Boolean>
*/
- private Pair<Bitmap, Boolean> generatePreview(BaseActivity launcher, WidgetItem item,
- Bitmap recycle,
- int previewWidth, int previewHeight) {
+ private Bitmap generatePreview(WidgetItem item, int previewWidth, int previewHeight) {
if (item.widgetInfo != null) {
- return generateWidgetPreview(launcher, item.widgetInfo,
- previewWidth, recycle, null);
+ return generateWidgetPreview(item.widgetInfo, previewWidth, null);
} else {
- return new Pair<>(generateShortcutPreview(launcher, item.activityInfo,
- previewWidth, previewHeight, recycle), false);
+ return generateShortcutPreview(item.activityInfo, previewWidth, previewHeight);
}
}
@@ -369,16 +104,12 @@
* Generates the widget preview from either the {@link WidgetManagerHelper} or cache
* and add badge at the bottom right corner.
*
- * @param launcher
* @param info information about the widget
* @param maxPreviewWidth width of the preview on either workspace or tray
- * @param preview bitmap that can be recycled
* @param preScaledWidthOut return the width of the returned bitmap
- * @return Pair<Bitmap (the preview) , Boolean (should be stored in db)>
*/
- public Pair<Bitmap, Boolean> generateWidgetPreview(BaseActivity launcher,
- LauncherAppWidgetProviderInfo info,
- int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) {
+ public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info,
+ int maxPreviewWidth, int[] preScaledWidthOut) {
// Load the preview image if possible
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
@@ -409,117 +140,97 @@
int previewWidth;
int previewHeight;
- boolean savePreviewImage = widgetPreviewExists || info.previewImage == 0;
+ DeviceProfile dp = ActivityContext.lookupContext(mContext).getDeviceProfile();
if (widgetPreviewExists && drawable.getIntrinsicWidth() > 0
&& drawable.getIntrinsicHeight() > 0) {
previewWidth = drawable.getIntrinsicWidth();
previewHeight = drawable.getIntrinsicHeight();
} else {
- DeviceProfile dp = launcher.getDeviceProfile();
Size widgetSize = WidgetSizes.getWidgetPaddedSizePx(mContext, info.provider, dp, spanX,
spanY);
previewWidth = widgetSize.getWidth();
previewHeight = widgetSize.getHeight();
}
- // Scale to fit width only - let the widget preview be clipped in the
- // vertical dimension
- float scale = 1f;
if (preScaledWidthOut != null) {
preScaledWidthOut[0] = previewWidth;
}
- if (previewWidth > maxPreviewWidth) {
- scale = maxPreviewWidth / (float) (previewWidth);
- }
+ // Scale to fit width only - let the widget preview be clipped in the
+ // vertical dimension
+ final float scale = previewWidth > maxPreviewWidth
+ ? (maxPreviewWidth / (float) (previewWidth)) : 1f;
if (scale != 1f) {
previewWidth = Math.max((int) (scale * previewWidth), 1);
previewHeight = Math.max((int) (scale * previewHeight), 1);
}
- final Canvas c = new Canvas();
- if (preview == null) {
- // If no bitmap was provided, then allocate a new one with the right size.
- preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
- c.setBitmap(preview);
- } else {
- // If a bitmap was passed in, attempt to reconfigure the bitmap to the same dimensions
- // as the preview.
- try {
- preview.reconfigure(previewWidth, previewHeight, preview.getConfig());
- } catch (IllegalArgumentException e) {
- // This occurs if the preview can't be reconfigured for any reason. In this case,
- // allocate a new bitmap with the right size.
- preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
- }
+ final int previewWidthF = previewWidth;
+ final int previewHeightF = previewHeight;
+ final Drawable drawableF = drawable;
- c.setBitmap(preview);
- c.drawColor(0, PorterDuff.Mode.CLEAR);
- }
-
- // Draw the scaled preview into the final bitmap
- if (widgetPreviewExists) {
- drawable.setBounds(0, 0, previewWidth, previewHeight);
- drawable.draw(c);
- } else {
- RectF boxRect;
-
- // Draw horizontal and vertical lines to represent individual columns.
- final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- if (Utilities.ATLEAST_S) {
- boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
- previewWidth, /* bottom= */ previewHeight);
-
- p.setStyle(Paint.Style.FILL);
- p.setColor(Color.WHITE);
- float roundedCorner = mContext.getResources().getDimension(
- android.R.dimen.system_app_widget_background_radius);
- c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
+ return BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight, c -> {
+ // Draw the scaled preview into the final bitmap
+ if (widgetPreviewExists) {
+ drawableF.setBounds(0, 0, previewWidthF, previewHeightF);
+ drawableF.draw(c);
} else {
- boxRect = drawBoxWithShadow(c, previewWidth, previewHeight);
- }
+ RectF boxRect;
- p.setStyle(Paint.Style.STROKE);
- p.setStrokeWidth(mContext.getResources()
- .getDimension(R.dimen.widget_preview_cell_divider_width));
- p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ // Draw horizontal and vertical lines to represent individual columns.
+ final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
- float t = boxRect.left;
- float tileSize = boxRect.width() / spanX;
- for (int i = 1; i < spanX; i++) {
- t += tileSize;
- c.drawLine(t, 0, t, previewHeight, p);
- }
+ if (Utilities.ATLEAST_S) {
+ boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
+ previewWidthF, /* bottom= */ previewHeightF);
- t = boxRect.top;
- tileSize = boxRect.height() / spanY;
- for (int i = 1; i < spanY; i++) {
- t += tileSize;
- c.drawLine(0, t, previewWidth, t, p);
- }
-
- // Draw icon in the center.
- try {
- Drawable icon =
- mIconCache.getFullResIcon(info.provider.getPackageName(), info.icon);
- if (icon != null) {
- int appIconSize = launcher.getDeviceProfile().iconSizePx;
- int iconSize = (int) Math.min(appIconSize * scale,
- Math.min(boxRect.width(), boxRect.height()));
-
- icon = mutateOnMainThread(icon);
- int hoffset = (previewWidth - iconSize) / 2;
- int yoffset = (previewHeight - iconSize) / 2;
- icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
- icon.draw(c);
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(Color.WHITE);
+ float roundedCorner = mContext.getResources().getDimension(
+ android.R.dimen.system_app_widget_background_radius);
+ c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
+ } else {
+ boxRect = drawBoxWithShadow(c, previewWidthF, previewHeightF);
}
- } catch (Resources.NotFoundException e) {
- savePreviewImage = false;
+
+ p.setStyle(Paint.Style.STROKE);
+ p.setStrokeWidth(mContext.getResources()
+ .getDimension(R.dimen.widget_preview_cell_divider_width));
+ p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ float t = boxRect.left;
+ float tileSize = boxRect.width() / spanX;
+ for (int i = 1; i < spanX; i++) {
+ t += tileSize;
+ c.drawLine(t, 0, t, previewHeightF, p);
+ }
+
+ t = boxRect.top;
+ tileSize = boxRect.height() / spanY;
+ for (int i = 1; i < spanY; i++) {
+ t += tileSize;
+ c.drawLine(0, t, previewWidthF, t, p);
+ }
+
+ // Draw icon in the center.
+ try {
+ Drawable icon = LauncherAppState.getInstance(mContext).getIconCache()
+ .getFullResIcon(info.provider.getPackageName(), info.icon);
+ if (icon != null) {
+ int appIconSize = dp.iconSizePx;
+ int iconSize = (int) Math.min(appIconSize * scale,
+ Math.min(boxRect.width(), boxRect.height()));
+
+ icon = mutateOnMainThread(icon);
+ int hoffset = (previewWidthF - iconSize) / 2;
+ int yoffset = (previewHeightF - iconSize) / 2;
+ icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize);
+ icon.draw(c);
+ }
+ } catch (Resources.NotFoundException e) {
+ }
}
- c.setBitmap(null);
- }
- return new Pair<>(preview, savePreviewImage);
+ });
}
private RectF drawBoxWithShadow(Canvas c, int width, int height) {
@@ -537,42 +248,29 @@
return builder.bounds;
}
- private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info,
- int maxWidth, int maxHeight, Bitmap preview) {
- int iconSize = launcher.getDeviceProfile().allAppsIconSizePx;
- int padding = launcher.getResources()
+ private Bitmap generateShortcutPreview(
+ ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) {
+ int iconSize = ActivityContext.lookupContext(mContext).getDeviceProfile().allAppsIconSizePx;
+ int padding = mContext.getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
int size = iconSize + 2 * padding;
if (maxHeight < size || maxWidth < size) {
throw new RuntimeException("Max size is too small for preview");
}
- final Canvas c = new Canvas();
- if (preview == null || preview.getWidth() < size || preview.getHeight() < size) {
- preview = Bitmap.createBitmap(size, size, Config.ARGB_8888);
- c.setBitmap(preview);
- } else {
- if (preview.getWidth() > size || preview.getHeight() > size) {
- preview.reconfigure(size, size, preview.getConfig());
- }
+ return BitmapRenderer.createHardwareBitmap(size, size, c -> {
+ drawBoxWithShadow(c, size, size);
- // Reusing bitmap. Clear it.
- c.setBitmap(preview);
- c.drawColor(0, PorterDuff.Mode.CLEAR);
- }
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ Drawable icon = li.createBadgedIconBitmap(
+ mutateOnMainThread(info.getFullResIcon(
+ LauncherAppState.getInstance(mContext).getIconCache())),
+ Process.myUserHandle(), 0).newIcon(mContext);
+ li.recycle();
- drawBoxWithShadow(c, size, size);
-
- LauncherIcons li = LauncherIcons.obtain(mContext);
- Drawable icon = li.createBadgedIconBitmap(
- mutateOnMainThread(info.getFullResIcon(mIconCache)),
- Process.myUserHandle(), 0).newIcon(launcher);
- li.recycle();
-
- icon.setBounds(padding, padding, padding + iconSize, padding + iconSize);
- icon.draw(c);
- c.setBitmap(null);
- return preview;
+ icon.setBounds(padding, padding, padding + iconSize, padding + iconSize);
+ icon.draw(c);
+ });
}
private Drawable mutateOnMainThread(final Drawable drawable) {
@@ -585,206 +283,4 @@
throw new RuntimeException(e);
}
}
-
- /**
- * @return an array of containing versionCode and lastUpdatedTime for the package.
- */
- @Thunk long[] getPackageVersion(String packageName) {
- synchronized (mPackageVersions) {
- long[] versions = mPackageVersions.get(packageName);
- if (versions == null) {
- versions = new long[2];
- try {
- PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES);
- versions[0] = info.versionCode;
- versions[1] = info.lastUpdateTime;
- } catch (NameNotFoundException e) {
- Log.e(TAG, "PackageInfo not found", e);
- }
- mPackageVersions.put(packageName, versions);
- }
- return versions;
- }
- }
-
- private class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
- implements CancellationSignal.OnCancelListener {
- @Thunk final WidgetCacheKey mKey;
- private final WidgetItem mInfo;
- private final int mPreviewHeight;
- private final int mPreviewWidth;
- private final WidgetPreviewLoadedCallback mCallback;
- private final BaseActivity mActivity;
- @Thunk long[] mVersions;
- @Thunk Bitmap mBitmapToRecycle;
-
- @Nullable private Bitmap mUnusedPreviewBitmap;
- private boolean mSaveToDB = false;
-
- PreviewLoadTask(BaseActivity activity, WidgetCacheKey key, WidgetItem info,
- int previewWidth, int previewHeight, WidgetPreviewLoadedCallback callback) {
- mActivity = activity;
- mKey = key;
- mInfo = info;
- mPreviewHeight = previewHeight;
- mPreviewWidth = previewWidth;
- mCallback = callback;
- if (DEBUG) {
- Log.d(TAG, String.format("%s, %s, %d, %d",
- mKey, mInfo, mPreviewHeight, mPreviewWidth));
- }
- }
-
- @Override
- protected Bitmap doInBackground(Void... params) {
- Bitmap unusedBitmap = null;
-
- // If already cancelled before this gets to run in the background, then return early
- if (isCancelled()) {
- return null;
- }
- synchronized (mUnusedBitmaps) {
- // Check if we can re-use a bitmap
- for (Bitmap candidate : mUnusedBitmaps) {
- if (candidate != null && candidate.isMutable()
- && candidate.getWidth() == mPreviewWidth
- && candidate.getHeight() == mPreviewHeight) {
- unusedBitmap = candidate;
- mUnusedBitmaps.remove(unusedBitmap);
- break;
- }
- }
- }
-
- // creating a bitmap is expensive. Do not do this inside synchronized block.
- if (unusedBitmap == null) {
- unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888);
- }
- // If cancelled now, don't bother reading the preview from the DB
- if (isCancelled()) {
- return unusedBitmap;
- }
- Bitmap preview = readFromDb(mKey, unusedBitmap, this);
- // Only consider generating the preview if we have not cancelled the task already
- if (!isCancelled() && preview == null) {
- // Fetch the version info before we generate the preview, so that, in-case the
- // app was updated while we are generating the preview, we use the old version info,
- // which would gets re-written next time.
- boolean persistable = mInfo.activityInfo == null
- || mInfo.activityInfo.isPersistable();
- mVersions = persistable ? getPackageVersion(mKey.componentName.getPackageName())
- : null;
-
- // it's not in the db... we need to generate it
- Pair<Bitmap, Boolean> pair = generatePreview(mActivity, mInfo, unusedBitmap,
- mPreviewWidth, mPreviewHeight);
- preview = pair.first;
-
- if (preview != unusedBitmap) {
- mUnusedPreviewBitmap = unusedBitmap;
- }
-
- this.mSaveToDB = pair.second;
- }
- return preview;
- }
-
- @Override
- protected void onPostExecute(final Bitmap preview) {
- mCallback.onPreviewLoaded(preview);
-
- // Write the generated preview to the DB in the worker thread
- if (mVersions != null) {
- MODEL_EXECUTOR.post(new Runnable() {
- @Override
- public void run() {
- if (mUnusedPreviewBitmap != null) {
- // If we didn't end up using the bitmap, it can be added back into the
- // recycled set.
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(mUnusedPreviewBitmap);
- }
- }
-
- if (!isCancelled() && mSaveToDB) {
- // If we are still using this preview, then write it to the DB and then
- // let the normal clear mechanism recycle the bitmap
- writeToDb(mKey, mVersions, preview);
- mBitmapToRecycle = preview;
- } else {
- // If we've already cancelled, then skip writing the bitmap to the DB
- // and manually add the bitmap back to the recycled set
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(preview);
- }
- }
- }
- });
- } else {
- // If we don't need to write to disk, then ensure the preview gets recycled by
- // the normal clear mechanism
- mBitmapToRecycle = preview;
- }
- }
-
- @Override
- protected void onCancelled(final Bitmap preview) {
- // If we've cancelled while the task is running, then can return the bitmap to the
- // recycled set immediately. Otherwise, it will be recycled after the preview is written
- // to disk.
- if (preview != null) {
- MODEL_EXECUTOR.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(preview);
- }
- }
- });
- }
- }
-
- @Override
- public void onCancel() {
- cancel(true);
-
- // This only handles the case where the PreviewLoadTask is cancelled after the task has
- // successfully completed (including having written to disk when necessary). In the
- // other cases where it is cancelled while the task is running, it will be cleaned up
- // in the tasks's onCancelled() call, and if cancelled while the task is writing to
- // disk, it will be cancelled in the task's onPostExecute() call.
- if (mBitmapToRecycle != null) {
- MODEL_EXECUTOR.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mUnusedBitmaps) {
- mUnusedBitmaps.add(mBitmapToRecycle);
- }
- mBitmapToRecycle = null;
- }
- });
- }
- }
- }
-
- private static final class WidgetCacheKey extends ComponentKey {
-
- @Thunk final String mSize;
-
- WidgetCacheKey(ComponentName componentName, UserHandle user, String size) {
- super(componentName, user);
- this.mSize = size;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode() ^ mSize.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- return super.equals(o) && ((WidgetCacheKey) o).mSize.equals(mSize);
- }
- }
}
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index d12fe74..241c937 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -49,6 +49,8 @@
*/
private final PointF mTranslationForCentering = new PointF(0, 0);
+ private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
+
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
@@ -167,9 +169,9 @@
private void updateTranslation() {
super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
- + mTranslationForCentering.x);
+ + mTranslationForCentering.x + mTranslationForMoveFromCenterAnimation.x);
super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
- + mTranslationForCentering.y);
+ + mTranslationForCentering.y + mTranslationForMoveFromCenterAnimation.y);
}
public void setTranslationForCentering(float x, float y) {
@@ -177,6 +179,11 @@
updateTranslation();
}
+ public void setTranslationForMoveFromCenterAnimation(float x, float y) {
+ mTranslationForMoveFromCenterAnimation.set(x, y);
+ updateTranslation();
+ }
+
public void setReorderBounceOffset(float x, float y) {
mTranslationForReorderBounce.set(x, y);
updateTranslation();
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index d3a0190..57a6d3f 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -85,7 +85,7 @@
setBackgroundResource(R.drawable.pending_widget_bg);
setWillNotDraw(false);
- updateAppWidget(null);
+ super.updateAppWidget(null);
setOnClickListener(ItemClickHandler.INSTANCE);
if (info.pendingItemInfo == null) {
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 991910d..463f4ac 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.util.Size;
import android.view.View;
import android.view.View.MeasureSpec;
@@ -41,6 +42,7 @@
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.RoundDrawableWrapper;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
import com.android.launcher3.widget.util.WidgetSizes;
@@ -94,6 +96,9 @@
*/
public void startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth,
Point screenPos, DragSource source, DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "3");
+ }
final Launcher launcher = Launcher.getLauncher(mView.getContext());
LauncherAppState app = LauncherAppState.getInstance(launcher);
@@ -140,10 +145,9 @@
.addDragListener(new AppWidgetHostViewDragListener(launcher));
}
if (preview == null && mAppWidgetHostViewPreview == null) {
- Drawable p = new FastBitmapDrawable(
- app.getWidgetCache().generateWidgetPreview(launcher,
- createWidgetInfo.info, maxWidth, null,
- previewSizeBeforeScale).first);
+ Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher)
+ .generateWidgetPreview(
+ createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 167eb09..f1ac656 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -26,14 +26,12 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.os.CancellationSignal;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -42,18 +40,22 @@
import android.widget.RemoteViews;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.RoundDrawableWrapper;
+import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.util.WidgetSizes;
+import java.util.function.Consumer;
+
/**
* Represents the individual cell of the widget inside the widget tray. The preview is drawn
* horizontally centered, and scaled down if needed.
@@ -63,7 +65,7 @@
* transition from the view to drag view, so when adding padding support, DnD would need to
* consider the appropriate scaling factor.
*/
-public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
+public class WidgetCell extends LinearLayout {
private static final String TAG = "WidgetCell";
private static final boolean DEBUG = false;
@@ -115,15 +117,12 @@
protected WidgetItem mItem;
- private WidgetPreviewLoader mWidgetPreviewLoader;
+ private final DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- protected CancellationSignal mActiveRequest;
+ protected HandlerRunnable mActiveRequest;
private boolean mAnimatePreview = true;
- private boolean mApplyBitmapDeferred = false;
- private Drawable mDeferredDrawable;
-
- protected final BaseActivity mActivity;
+ protected final ActivityContext mActivity;
private final CheckLongPressHelper mLongPressHelper;
private final float mEnforcedCornerRadius;
@@ -143,7 +142,8 @@
public WidgetCell(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mActivity = BaseActivity.fromContext(context);
+ mActivity = ActivityContext.lookupContext(context);
+ mWidgetPreviewLoader = new DatabaseWidgetPreviewLoader(context);
mLongPressHelper = new CheckLongPressHelper(this);
mLongPressHelper.setLongPressTimeoutFactor(1);
@@ -218,7 +218,36 @@
this.mSourceContainer = sourceContainer;
}
- public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
+ /**
+ * Applies the item to this view
+ */
+ public void applyFromCellItem(WidgetItem item) {
+ applyFromCellItem(item, 1f);
+ }
+
+ /**
+ * Applies the item to this view
+ */
+ public void applyFromCellItem(WidgetItem item, float previewScale) {
+ applyFromCellItem(item, previewScale, this::applyPreview, null);
+ }
+
+ /**
+ * Applies the item to this view
+ * @param item item to apply
+ * @param previewScale factor to scale the preview
+ * @param callback callback when preview is loaded in case the preview is being loaded or cached
+ * @param cachedPreview previously cached preview bitmap is present
+ */
+ public void applyFromCellItem(WidgetItem item, float previewScale,
+ @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
+ // setPreviewSize
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, item);
+ mTargetPreviewWidth = widgetSize.getWidth();
+ mTargetPreviewHeight = widgetSize.getHeight();
+ mPreviewContainerScale = previewScale;
+
applyPreviewOnAppWidgetHostView(item);
Context context = getContext();
@@ -240,14 +269,14 @@
}
}
- mWidgetPreviewLoader = loader;
if (item.activityInfo != null) {
setTag(new PendingAddShortcutInfo(item.activityInfo));
} else {
setTag(new PendingAddWidgetInfo(item.widgetInfo, mSourceContainer));
}
- }
+ ensurePreviewWithCallback(callback, cachedPreview);
+ }
private void applyPreviewOnAppWidgetHostView(WidgetItem item) {
if (mRemoteViewsPreview != null) {
@@ -294,41 +323,19 @@
return mAppWidgetHostViewPreview;
}
- /**
- * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
- * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
- * ready.
- * This prevents invalidates while the animation is running.
- */
- public void setApplyBitmapDeferred(boolean isDeferred) {
- if (mApplyBitmapDeferred != isDeferred) {
- mApplyBitmapDeferred = isDeferred;
- if (!mApplyBitmapDeferred && mDeferredDrawable != null) {
- applyPreview(mDeferredDrawable);
- mDeferredDrawable = null;
- }
- }
- }
-
public void setAnimatePreview(boolean shouldAnimate) {
mAnimatePreview = shouldAnimate;
}
- public void applyPreview(Bitmap bitmap) {
- FastBitmapDrawable drawable = new FastBitmapDrawable(bitmap);
- applyPreview(new RoundDrawableWrapper(drawable, mEnforcedCornerRadius));
- }
+ private void applyPreview(Bitmap bitmap) {
+ if (bitmap != null) {
+ Drawable drawable = new RoundDrawableWrapper(
+ new FastBitmapDrawable(bitmap), mEnforcedCornerRadius);
- private void applyPreview(Drawable drawable) {
- if (mApplyBitmapDeferred) {
- mDeferredDrawable = drawable;
- return;
- }
- if (drawable != null) {
+ // Scale down the preview size if it's wider than the cell.
float scale = 1f;
- if (getWidth() > 0 && getHeight() > 0) {
- // Scale down the preview size if it's wider than the cell.
- float maxWidth = getWidth();
+ if (mTargetPreviewWidth > 0) {
+ float maxWidth = mTargetPreviewWidth;
float previewWidth = drawable.getIntrinsicWidth() * mPreviewContainerScale;
scale = Math.min(maxWidth / previewWidth, 1);
}
@@ -349,6 +356,10 @@
} else {
mWidgetImageContainer.setAlpha(1f);
}
+ if (mActiveRequest != null) {
+ mActiveRequest.cancel();
+ mActiveRequest = null;
+ }
}
private void setContainerSize(int width, int height) {
@@ -358,7 +369,13 @@
mWidgetImageContainer.setLayoutParams(layoutParams);
}
- public void ensurePreview() {
+ /**
+ * Ensures that the preview is already loaded or being loaded. If the preview is not loaded,
+ * it applies the provided cachedPreview. If that is null, it starts a loader and notifies the
+ * callback on successful load.
+ */
+ private void ensurePreviewWithCallback(Consumer<Bitmap> callback,
+ @Nullable Bitmap cachedPreview) {
if (mAppWidgetHostViewPreview != null) {
int containerWidth = (int) (mTargetPreviewWidth * mPreviewContainerScale);
int containerHeight = (int) (mTargetPreviewHeight * mPreviewContainerScale);
@@ -382,38 +399,18 @@
mAppWidgetHostViewPreview.setLayoutParams(params);
mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
mWidgetImage.setVisibility(View.GONE);
- applyPreview((Drawable) null);
+ applyPreview(null);
+ return;
+ }
+ if (cachedPreview != null) {
+ applyPreview(cachedPreview);
return;
}
if (mActiveRequest != null) {
return;
}
mActiveRequest = mWidgetPreviewLoader.loadPreview(
- BaseActivity.fromContext(getContext()), mItem,
- new Size(mTargetPreviewWidth, mTargetPreviewHeight),
- this::applyPreview);
- }
-
- /** Sets the widget preview image size in number of cells. */
- public Size setPreviewSize(WidgetItem widgetItem) {
- return setPreviewSize(widgetItem, 1f);
- }
-
- /** Sets the widget preview image size, in number of cells, and preview scale. */
- public Size setPreviewSize(WidgetItem widgetItem, float previewScale) {
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, widgetItem);
- mTargetPreviewWidth = widgetSize.getWidth();
- mTargetPreviewHeight = widgetSize.getHeight();
- mPreviewContainerScale = previewScale;
- return widgetSize;
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- removeOnLayoutChangeListener(this);
- ensurePreview();
+ mItem, new Size(mTargetPreviewWidth, mTargetPreviewHeight), callback);
}
@Override
@@ -429,17 +426,6 @@
mLongPressHelper.cancelLongPress();
}
- /**
- * Helper method to get the string info of the tag.
- */
- private String getTagToString() {
- if (getTag() instanceof PendingAddWidgetInfo ||
- getTag() instanceof PendingAddShortcutInfo) {
- return getTag().toString();
- }
- return "";
- }
-
private static NavigableAppWidgetHostView createAppWidgetHostView(Context context) {
return new NavigableAppWidgetHostView(context) {
@Override
@@ -450,12 +436,7 @@
}
private static boolean isLauncherContext(Context context) {
- try {
- Launcher.getLauncher(context);
- return true;
- } catch (Exception e) {
- return false;
- }
+ return ActivityContext.lookupContext(context) instanceof Launcher;
}
@Override
@@ -483,6 +464,20 @@
mAppWidgetHostViewPreview.measure(
makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED),
makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED));
+ if (mRemoteViewsPreview != null) {
+ // If RemoteViews contains multiple sizes, the best fit sized RemoteViews will be
+ // selected in onLayout. To work out the right measurement, let's layout and then
+ // measure again.
+ mAppWidgetHostViewPreview.layout(
+ /* left= */ 0,
+ /* top= */ 0,
+ /* right= */ mTargetPreviewWidth,
+ /* bottom= */ mTargetPreviewHeight);
+ mAppWidgetHostViewPreview.measure(
+ makeMeasureSpec(mTargetPreviewWidth, MeasureSpec.UNSPECIFIED),
+ makeMeasureSpec(mTargetPreviewHeight, MeasureSpec.UNSPECIFIED));
+
+ }
View widgetContent = mAppWidgetHostViewPreview.getChildAt(0);
int appWidgetContentWidth = widgetContent.getMeasuredWidth();
int appWidgetContentHeight = widgetContent.getMeasuredHeight();
diff --git a/src/com/android/launcher3/widget/WidgetPreviewLoader.java b/src/com/android/launcher3/widget/WidgetPreviewLoader.java
deleted file mode 100644
index ff5c82f..0000000
--- a/src/com/android/launcher3/widget/WidgetPreviewLoader.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget;
-
-import android.graphics.Bitmap;
-import android.os.CancellationSignal;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.model.WidgetItem;
-
-/** Asynchronous loader of preview bitmaps for {@link WidgetItem}s. */
-public interface WidgetPreviewLoader {
- /**
- * Loads a widget preview and calls back to {@code callback} when complete.
- *
- * @return a {@link CancellationSignal} which can be used to cancel the request.
- */
- @NonNull
- @UiThread
- CancellationSignal loadPreview(
- @NonNull BaseActivity activity,
- @NonNull WidgetItem item,
- @NonNull Size previewSize,
- @NonNull WidgetPreviewLoadedCallback callback);
-
- /** Callback class for requests to {@link WidgetPreviewLoader}. */
- interface WidgetPreviewLoadedCallback {
- void onPreviewLoaded(@NonNull Bitmap preview);
- }
-}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 6beff3a..bb4638a 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -37,7 +37,6 @@
import android.widget.TextView;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.WidgetItem;
@@ -199,11 +198,7 @@
tableRow.setGravity(Gravity.TOP);
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
- widget.setPreviewSize(widgetItem);
- widget.applyFromCellItem(widgetItem, LauncherAppState.getInstance(mActivityContext)
- .getWidgetCache());
- widget.ensurePreview();
- widget.setVisibility(View.VISIBLE);
+ widget.applyFromCellItem(widgetItem);
});
widgetsTable.addView(tableRow);
});
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 329a444..2e2a968 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -33,6 +33,7 @@
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.systemui.plugins.CustomWidgetPlugin;
@@ -46,7 +47,7 @@
/**
* CustomWidgetManager handles custom widgets implemented as a plugin.
*/
-public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> {
+public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, SafeCloseable {
public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
new MainThreadInitializedObject<>(CustomWidgetManager::new);
@@ -71,7 +72,8 @@
.addPluginListener(this, CustomWidgetPlugin.class, true);
}
- public void onDestroy() {
+ @Override
+ public void close() {
PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this);
}
diff --git a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
new file mode 100644
index 0000000..e62425f
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.model;
+
+import com.android.launcher3.model.data.PackageItemInfo;
+
+import java.util.Collections;
+
+/**
+ * Entry representing the top empty space
+ */
+public class WidgetListSpaceEntry extends WidgetsListBaseEntry {
+
+ public WidgetListSpaceEntry() {
+ super(new PackageItemInfo(""), "", Collections.EMPTY_LIST);
+ mPkgItem.title = "";
+ }
+
+ @Override
+ public int getRank() {
+ return RANK_WIDGETS_TOP_SPACE;
+ }
+}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index abc79ff..1d1c9dc 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -73,11 +73,13 @@
}
@Retention(SOURCE)
- @IntDef({RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER, RANK_WIDGETS_LIST_CONTENT})
+ @IntDef({RANK_WIDGETS_TOP_SPACE, RANK_WIDGETS_LIST_HEADER, RANK_WIDGETS_LIST_SEARCH_HEADER,
+ RANK_WIDGETS_LIST_CONTENT})
public @interface Rank {
}
- public static final int RANK_WIDGETS_LIST_HEADER = 1;
- public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 2;
- public static final int RANK_WIDGETS_LIST_CONTENT = 3;
+ public static final int RANK_WIDGETS_TOP_SPACE = 1;
+ public static final int RANK_WIDGETS_LIST_HEADER = 2;
+ public static final int RANK_WIDGETS_LIST_SEARCH_HEADER = 3;
+ public static final int RANK_WIDGETS_LIST_CONTENT = 4;
}
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index 6643779..716dcf3 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -15,297 +15,148 @@
*/
package com.android.launcher3.widget.picker;
-import android.animation.ValueAnimator;
-import android.graphics.Point;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.widget.RelativeLayout;
+import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
-import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.widget.picker.WidgetsFullSheet.SearchAndRecommendationViewHolder;
-import com.android.launcher3.workprofile.PersonalWorkPagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
/**
* A controller which measures & updates {@link WidgetsFullSheet}'s views padding, margin and
* vertical displacement upon scrolling.
*/
final class SearchAndRecommendationsScrollController implements
- RecyclerViewFastScroller.OnFastScrollChangeListener, ValueAnimator.AnimatorUpdateListener {
- private final boolean mHasWorkProfile;
- private final SearchAndRecommendationViewHolder mViewHolder;
- private final View mSearchAndRecommendationViewParent;
- private final WidgetsRecyclerView mPrimaryRecyclerView;
- private final WidgetsRecyclerView mSearchRecyclerView;
- private final TextView mNoWidgetsView;
- private final int mTabsHeight;
- private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
- private final Point mTempOffset = new Point();
- private int mBottomInset;
+ RecyclerView.OnChildAttachStateChangeListener {
- // The following are only non null if mHasWorkProfile is true.
- @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
- @Nullable private final View mPrimaryWorkTabsView;
- @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
+ private static final FloatProperty<SearchAndRecommendationsScrollController> SCROLL_OFFSET =
+ new FloatProperty<SearchAndRecommendationsScrollController>("scrollAnimOffset") {
+ @Override
+ public void setValue(SearchAndRecommendationsScrollController controller, float offset) {
+ controller.mScrollOffset = offset;
+ controller.updateHeaderScroll();
+ }
+
+ @Override
+ public Float get(SearchAndRecommendationsScrollController controller) {
+ return controller.mScrollOffset;
+ }
+ };
+
+ private static final MotionEventProxyMethod INTERCEPT_PROXY = ViewGroup::onInterceptTouchEvent;
+ private static final MotionEventProxyMethod TOUCH_PROXY = ViewGroup::onTouchEvent;
+
+ final SearchAndRecommendationsView mContainer;
+ final View mSearchBarContainer;
+ final WidgetsSearchBar mSearchBar;
+ final TextView mHeaderTitle;
+ final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
+ @Nullable final View mTabBar;
private WidgetsRecyclerView mCurrentRecyclerView;
- private int mCurrentRecyclerViewScrollY = 0;
+ private EmptySpaceView mCurrentEmptySpaceView;
- private OnContentChangeListener mOnContentChangeListener = () -> onScrollChanged();
-
- /**
- * The vertical distance, in pixels, until the search is pinned at the top of the screen when
- * the user scrolls down the recycler view.
- */
- private int mCollapsibleHeightForSearch = 0;
- /**
- * The vertical distance, in pixels, until the recommendation table disappears from the top of
- * the screen when the user scrolls down the recycler view.
- */
- private int mCollapsibleHeightForRecommendation = 0;
- /**
- * The vertical distance, in pixels, until the tabs is pinned at the top of the screen when the
- * user scrolls down the recycler view.
- *
- * <p>Always 0 if there is no work profile.
- */
- private int mCollapsibleHeightForTabs = 0;
+ private float mLastScroll = 0;
+ private float mScrollOffset = 0;
+ private Animator mOffsetAnimator;
private boolean mShouldForwardToRecyclerView = false;
+ private int mHeaderHeight;
+
SearchAndRecommendationsScrollController(
- boolean hasWorkProfile,
- int tabsHeight,
- SearchAndRecommendationViewHolder viewHolder,
- WidgetsRecyclerView primaryRecyclerView,
- @Nullable WidgetsRecyclerView workRecyclerView,
- WidgetsRecyclerView searchRecyclerView,
- @Nullable View personalWorkTabsView,
- @Nullable PersonalWorkPagedView primaryWorkViewPager,
- TextView noWidgetsView) {
- mHasWorkProfile = hasWorkProfile;
- mViewHolder = viewHolder;
- mViewHolder.mContainer.setSearchAndRecommendationScrollController(this);
- mSearchAndRecommendationViewParent = (View) mViewHolder.mContainer.getParent();
- mPrimaryRecyclerView = primaryRecyclerView;
- mWorkRecyclerView = workRecyclerView;
- mSearchRecyclerView = searchRecyclerView;
- mPrimaryWorkTabsView = personalWorkTabsView;
- mPrimaryWorkViewPager = primaryWorkViewPager;
- mTabsHeight = tabsHeight;
- mNoWidgetsView = noWidgetsView;
- setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false);
+ SearchAndRecommendationsView searchAndRecommendationContainer) {
+ mContainer = searchAndRecommendationContainer;
+ mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
+ mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
+ mHeaderTitle = mContainer.findViewById(R.id.title);
+ mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
+ mTabBar = mContainer.findViewById(R.id.tabs);
+
+ mContainer.setSearchAndRecommendationScrollController(this);
}
public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
- setCurrentRecyclerView(currentRecyclerView, /* animateReset= */ true);
- }
-
- /** Sets the current active {@link WidgetsRecyclerView}. */
- private void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView,
- boolean animateReset) {
- if (mCurrentRecyclerView == currentRecyclerView) {
- return;
- }
+ boolean animateReset = mCurrentRecyclerView != null;
if (mCurrentRecyclerView != null) {
- mCurrentRecyclerView.setOnContentChangeListener(null);
+ mCurrentRecyclerView.removeOnChildAttachStateChangeListener(this);
}
mCurrentRecyclerView = currentRecyclerView;
- mCurrentRecyclerView.setOnContentChangeListener(mOnContentChangeListener);
+ mCurrentRecyclerView.addOnChildAttachStateChangeListener(this);
+ findCurrentEmptyView();
reset(animateReset);
}
- /**
- * Updates padding of {@link WidgetsFullSheet} contents to include {@code bottomInset} wherever
- * necessary.
- */
- public boolean updateBottomInset(int bottomInset) {
- mBottomInset = bottomInset;
- return updateMarginAndPadding();
+ public int getHeaderHeight() {
+ return mHeaderHeight;
+ }
+
+ private void updateHeaderScroll() {
+ mLastScroll = getCurrentScroll();
+ mHeaderTitle.setTranslationY(mLastScroll);
+ mRecommendedWidgetsTable.setTranslationY(mLastScroll);
+
+ float searchYDisplacement = Math.max(mLastScroll, -mSearchBarContainer.getTop());
+ mSearchBarContainer.setTranslationY(searchYDisplacement);
+
+ if (mTabBar != null) {
+ float tabsDisplacement = Math.max(mLastScroll, -mTabBar.getTop()
+ + mSearchBarContainer.getHeight());
+ mTabBar.setTranslationY(tabsDisplacement);
+ }
+ }
+
+ private float getCurrentScroll() {
+ return mScrollOffset + (mCurrentEmptySpaceView == null ? 0 : mCurrentEmptySpaceView.getY());
}
/**
- * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
+ * Updates the scrollable header height
*
- * @return {@code true} if margins or/and padding of views in the search and recommendations
- * container have been updated.
+ * @return {@code true} if the header height or dependent property changed.
*/
- public boolean updateMarginAndPadding() {
- boolean hasMarginOrPaddingUpdated = false;
- mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
- mCollapsibleHeightForRecommendation =
- measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
- + measureHeightWithVerticalMargins((View) mViewHolder.mSearchBarContainer)
- + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
+ public boolean updateHeaderHeight() {
+ boolean hasSizeUpdated = false;
- int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
- int noWidgetsViewHeight = topContainerHeight - mBottomInset;
-
- if (mHasWorkProfile) {
- mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
- // In a work profile setup, the full widget sheet contains the following views:
- // ------- (pinned) -|
- // Widgets (collapsible) -|---> LinearLayout for search & recommendations
- // Search bar (pinned) -|
- // Widgets recommendation (collapsible)-|
- // Personal | Work (pinned)
- // View Pager
- //
- // Views after the search & recommendations are not bound by RelativelyLayout param.
- // To position them on the expected location, padding & margin are added to these views
-
- // Tabs should have a padding of the height of the search & recommendations container.
- RelativeLayout.LayoutParams tabsLayoutParams =
- (RelativeLayout.LayoutParams) mPrimaryWorkTabsView.getLayoutParams();
- tabsLayoutParams.topMargin = topContainerHeight;
- mPrimaryWorkTabsView.setLayoutParams(tabsLayoutParams);
-
- // Instead of setting the top offset directly, we split the top offset into two values:
- // 1. topOffsetAfterAllViewsCollapsed: this is the top offset after all collapsible
- // views are no longer visible on the screen.
- // This value is set as the margin for the view pager.
- // 2. mMaxCollapsibleDistance
- // This value is set as the padding for the recycler views in order to work with
- // clipToPadding="false", which is an attribute for not showing top / bottom padding
- // when a recycler view has not reached the top or bottom of the list.
- // e.g. a list of 10 entries, only 3 entries are visible at a time.
- // case 1: recycler view is scrolled to the top. Top padding is visible/
- // (top padding)
- // item 1
- // item 2
- // item 3
- //
- // case 2: recycler view is scrolled to the middle. No padding is visible.
- // item 4
- // item 5
- // item 6
- //
- // case 3: recycler view is scrolled to the end. bottom padding is visible.
- // item 8
- // item 9
- // item 10
- // (bottom padding): not set in this case.
- //
- // When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
- // mMaxCollapsibleDistance should equal to the top container height.
- int topOffsetAfterAllViewsCollapsed =
- topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
-
- if (mPrimaryWorkTabsView.getVisibility() == View.VISIBLE) {
- noWidgetsViewHeight += mTabsHeight;
- }
-
- RelativeLayout.LayoutParams viewPagerLayoutParams =
- (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
- if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
- viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
- mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
- hasMarginOrPaddingUpdated = true;
- }
-
- if (mPrimaryRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
- mPrimaryRecyclerView.setPadding(
- mPrimaryRecyclerView.getPaddingLeft(),
- mCollapsibleHeightForTabs,
- mPrimaryRecyclerView.getPaddingRight(),
- mPrimaryRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- if (mWorkRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
- mWorkRecyclerView.setPadding(
- mWorkRecyclerView.getPaddingLeft(),
- mCollapsibleHeightForTabs,
- mWorkRecyclerView.getPaddingRight(),
- mWorkRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- } else {
- if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
- mPrimaryRecyclerView.setPadding(
- mPrimaryRecyclerView.getPaddingLeft(),
- topContainerHeight,
- mPrimaryRecyclerView.getPaddingRight(),
- mPrimaryRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- }
- if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
- mSearchRecyclerView.setPadding(
- mSearchRecyclerView.getPaddingLeft(),
- topContainerHeight,
- mSearchRecyclerView.getPaddingRight(),
- mSearchRecyclerView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- if (mNoWidgetsView.getPaddingTop() != noWidgetsViewHeight) {
- mNoWidgetsView.setPadding(
- mNoWidgetsView.getPaddingLeft(),
- noWidgetsViewHeight,
- mNoWidgetsView.getPaddingRight(),
- mNoWidgetsView.getPaddingBottom());
- hasMarginOrPaddingUpdated = true;
- }
- return hasMarginOrPaddingUpdated;
- }
-
- @Override
- public void onScrollChanged() {
- int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
- if (recyclerViewYOffset < 0) return;
- mCurrentRecyclerViewScrollY = recyclerViewYOffset;
- if (mAnimator.isStarted()) {
- mAnimator.cancel();
- }
- applyVerticalTransition();
- }
-
- /**
- * Changes the displacement of collapsible views (e.g. title & widget recommendations) and fixed
- * views (e.g. recycler views, tabs) upon scrolling / content changes in the recycler view.
- */
- private void applyVerticalTransition() {
- if (mCollapsibleHeightForRecommendation > 0) {
- int yDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForRecommendation);
- mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
- mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
+ int headerHeight = mContainer.getMeasuredHeight();
+ if (headerHeight != mHeaderHeight) {
+ mHeaderHeight = headerHeight;
+ hasSizeUpdated = true;
}
- if (mCollapsibleHeightForSearch > 0) {
- int searchYDisplacement = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForSearch);
- mViewHolder.mSearchBarContainer.setTranslationY(searchYDisplacement);
+ if (mCurrentEmptySpaceView != null
+ && mCurrentEmptySpaceView.setFixedHeight(mHeaderHeight)) {
+ hasSizeUpdated = true;
}
-
- if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
- int yDisplacementForTabs = Math.max(-mCurrentRecyclerViewScrollY,
- -mCollapsibleHeightForTabs);
- mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
- }
+ return hasSizeUpdated;
}
/** Resets any previous view translation. */
public void reset(boolean animate) {
- if (mCurrentRecyclerViewScrollY == 0) {
- return;
- }
- if (mAnimator.isStarted()) {
- mAnimator.cancel();
+ if (mOffsetAnimator != null) {
+ mOffsetAnimator.cancel();
+ mOffsetAnimator = null;
}
- if (animate) {
- mAnimator.setIntValues(mCurrentRecyclerViewScrollY, 0);
- mAnimator.addUpdateListener(this);
- mAnimator.setDuration(300);
- mAnimator.start();
+ mScrollOffset = 0;
+ if (!animate) {
+ updateHeaderScroll();
} else {
- mCurrentRecyclerViewScrollY = 0;
- applyVerticalTransition();
+ float startValue = mLastScroll - getCurrentScroll();
+ mOffsetAnimator = ObjectAnimator.ofFloat(this, SCROLL_OFFSET, startValue, 0);
+ mOffsetAnimator.addListener(forEndCallback(() -> mOffsetAnimator = null));
+ mOffsetAnimator.start();
}
}
@@ -313,61 +164,60 @@
* Returns {@code true} if a touch event should be intercepted by this controller.
*/
public boolean onInterceptTouchEvent(MotionEvent event) {
- calculateMotionEventOffset(mTempOffset);
- event.offsetLocation(mTempOffset.x, mTempOffset.y);
- try {
- mShouldForwardToRecyclerView = mCurrentRecyclerView.onInterceptTouchEvent(event);
- return mShouldForwardToRecyclerView;
- } finally {
- event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
- }
+ return (mShouldForwardToRecyclerView = proxyMotionEvent(event, INTERCEPT_PROXY));
}
/**
* Returns {@code true} if this controller has intercepted and consumed a touch event.
*/
public boolean onTouchEvent(MotionEvent event) {
- if (mShouldForwardToRecyclerView) {
- calculateMotionEventOffset(mTempOffset);
- event.offsetLocation(mTempOffset.x, mTempOffset.y);
- try {
- return mCurrentRecyclerView.onTouchEvent(event);
- } finally {
- event.offsetLocation(-mTempOffset.x, -mTempOffset.y);
- }
- }
- return false;
+ return mShouldForwardToRecyclerView && proxyMotionEvent(event, TOUCH_PROXY);
}
- private void calculateMotionEventOffset(Point p) {
- p.x = mViewHolder.mContainer.getLeft() - mCurrentRecyclerView.getLeft()
- - mSearchAndRecommendationViewParent.getLeft();
- p.y = mViewHolder.mContainer.getTop() - mCurrentRecyclerView.getTop()
- - mSearchAndRecommendationViewParent.getTop();
- }
-
- /** private the height, in pixel, + the vertical margins of a given view. */
- private static int measureHeightWithVerticalMargins(View view) {
- if (view.getVisibility() != View.VISIBLE) {
- return 0;
+ private boolean proxyMotionEvent(MotionEvent event, MotionEventProxyMethod method) {
+ float dx = mCurrentRecyclerView.getLeft() - mContainer.getLeft();
+ float dy = mCurrentRecyclerView.getTop() - mContainer.getTop();
+ event.offsetLocation(dx, dy);
+ try {
+ return method.proxyEvent(mCurrentRecyclerView, event);
+ } finally {
+ event.offsetLocation(-dx, -dy);
}
- MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
- return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
- + marginLayoutParams.topMargin;
}
@Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mCurrentRecyclerViewScrollY = (Integer) animation.getAnimatedValue();
- applyVerticalTransition();
+ public void onChildViewAttachedToWindow(@NonNull View view) {
+ if (view instanceof EmptySpaceView) {
+ findCurrentEmptyView();
+ }
}
- /**
- * A listener to be notified when there is a content change in the recycler view that may affect
- * the relative position of the search and recommendation container.
- */
- public interface OnContentChangeListener {
- /** Notifies a content change in the recycler view. */
- void onContentChanged();
+ @Override
+ public void onChildViewDetachedFromWindow(@NonNull View view) {
+ if (view == mCurrentEmptySpaceView) {
+ findCurrentEmptyView();
+ }
+ }
+
+ private void findCurrentEmptyView() {
+ if (mCurrentEmptySpaceView != null) {
+ mCurrentEmptySpaceView.setOnYChangeCallback(null);
+ mCurrentEmptySpaceView = null;
+ }
+ int childCount = mCurrentRecyclerView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mCurrentRecyclerView.getChildAt(i);
+ if (view instanceof EmptySpaceView) {
+ mCurrentEmptySpaceView = (EmptySpaceView) view;
+ mCurrentEmptySpaceView.setFixedHeight(getHeaderHeight());
+ mCurrentEmptySpaceView.setOnYChangeCallback(this::updateHeaderScroll);
+ return;
+ }
+ }
+ }
+
+ private interface MotionEventProxyMethod {
+
+ boolean proxyEvent(ViewGroup view, MotionEvent event);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index be83f9a..09f0299 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -57,13 +57,12 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
-import com.android.launcher3.views.TopRoundedCornerView;
+import com.android.launcher3.views.SpringRelativeLayout;
import com.android.launcher3.views.WidgetsEduView;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.search.SearchModeListener;
-import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
import com.android.launcher3.widget.util.WidgetsTableUtils;
import com.android.launcher3.workprofile.PersonalWorkPagedView;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -79,7 +78,6 @@
public class WidgetsFullSheet extends BaseWidgetSheet
implements ProviderChangedListener, OnActivePageChangedListener,
WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
- private static final String TAG = WidgetsFullSheet.class.getSimpleName();
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
@@ -149,8 +147,6 @@
};
private final int mTabsHeight;
- private final int mViewPagerTopPadding;
- private final int mSearchAndRecommendationContainerBottomMargin;
private final int mWidgetSheetContentHorizontalPadding;
@Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
@@ -158,10 +154,8 @@
private boolean mIsInSearchMode;
private boolean mIsNoWidgetsViewNeeded;
private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
- private View mTabsView;
private TextView mNoWidgetsView;
- private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
- private SearchAndRecommendationsScrollController mSearchAndRecommendationsScrollController;
+ private SearchAndRecommendationsScrollController mSearchScrollController;
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -174,14 +168,6 @@
mTabsHeight = mHasWorkProfile
? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
: 0;
- mViewPagerTopPadding = mHasWorkProfile
- ? getContext().getResources()
- .getDimensionPixelSize(R.dimen.widget_picker_view_pager_top_padding)
- : 0;
- mSearchAndRecommendationContainerBottomMargin = resources.getDimensionPixelSize(
- mHasWorkProfile
- ? R.dimen.search_and_recommended_widgets_container_small_bottom_margin
- : R.dimen.search_and_recommended_widgets_container_bottom_margin);
mWidgetSheetContentHorizontalPadding = 2 * resources.getDimensionPixelSize(
R.dimen.widget_cell_horizontal_padding);
}
@@ -194,12 +180,11 @@
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.container);
- TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
: R.layout.widgets_full_sheet_recyclerview;
- layoutInflater.inflate(contentLayoutRes, springLayout, true);
+ layoutInflater.inflate(contentLayoutRes, mContent, true);
RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
@@ -209,7 +194,6 @@
mViewPager.initParentViews(this);
mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
- mTabsView = findViewById(R.id.tabs);
findViewById(R.id.tab_personal)
.setOnClickListener((View view) -> mViewPager.snapToPage(0));
findViewById(R.id.tab_work)
@@ -220,33 +204,18 @@
mViewPager = null;
}
- layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
- true);
mNoWidgetsView = findViewById(R.id.no_widgets_text);
- mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
+ mSearchScrollController = new SearchAndRecommendationsScrollController(
findViewById(R.id.search_and_recommendations_container));
- TopRoundedCornerView.LayoutParams layoutParams =
- (TopRoundedCornerView.LayoutParams)
- mSearchAndRecommendationViewHolder.mContainer.getLayoutParams();
- layoutParams.bottomMargin = mSearchAndRecommendationContainerBottomMargin;
- mSearchAndRecommendationViewHolder.mContainer.setLayoutParams(layoutParams);
- mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
- mHasWorkProfile,
- mTabsHeight,
- mSearchAndRecommendationViewHolder,
- findViewById(R.id.primary_widgets_list_view),
- mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
- findViewById(R.id.search_widgets_list_view),
- mTabsView,
- mViewPager,
- mNoWidgetsView);
- fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
-
+ mSearchScrollController.setCurrentRecyclerView(
+ findViewById(R.id.primary_widgets_list_view));
+ mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
+ mSearchScrollController.mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
onRecommendedWidgetsBound();
onWidgetsBound();
- mSearchAndRecommendationViewHolder.mSearchBar.initialize(
+ mSearchScrollController.mSearchBar.initialize(
mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
setUpEducationViewsIfNeeded();
@@ -270,12 +239,13 @@
reset();
resetExpandedHeaders();
mCurrentWidgetsRecyclerView = recyclerView;
- mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView);
+ mSearchScrollController.setCurrentRecyclerView(recyclerView);
}
}
private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
- boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.getItemCount() > 0;
+ // The first item is always an empty space entry. Look for any more items.
+ boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
mNoWidgetsView.setText(
@@ -291,7 +261,7 @@
mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
}
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
- mSearchAndRecommendationsScrollController.reset(/* animate= */ true);
+ mSearchScrollController.reset(/* animate= */ true);
}
@VisibleForTesting
@@ -340,7 +310,8 @@
if (mHasWorkProfile) {
setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
}
- mSearchAndRecommendationsScrollController.updateBottomInset(insets.bottom);
+ ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = insets.bottom;
+
if (insets.bottom > 0) {
setupNavBarColor();
} else {
@@ -360,7 +331,7 @@
@Override
protected void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx) {
- setContentViewChildHorizontalMargin(mSearchAndRecommendationViewHolder.mContainer,
+ setContentViewChildHorizontalMargin(mSearchScrollController.mContainer,
contentHorizontalMarginInPx);
if (mViewPager == null) {
setContentViewChildHorizontalMargin(
@@ -385,14 +356,14 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
if (updateMaxSpansPerRow()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+ if (mSearchScrollController.updateHeaderHeight()) {
doMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@@ -455,7 +426,7 @@
if (mHasWorkProfile) {
mViewPager.setVisibility(VISIBLE);
- mTabsView.setVisibility(VISIBLE);
+ mSearchScrollController.mTabBar.setVisibility(VISIBLE);
AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
onActivePageChanged(mViewPager.getCurrentPage());
@@ -465,9 +436,9 @@
// Update recommended widgets section so that it occupies appropriate space on screen to
// leave enough space for presence/absence of mNoWidgetsView.
boolean isNoWidgetsViewNeeded =
- mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.getItemCount() == 0
+ !mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.hasVisibleEntries()
|| (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK)
- .mWidgetsListAdapter.getItemCount() == 0);
+ .mWidgetsListAdapter.hasVisibleEntries());
if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) {
mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded;
onRecommendedWidgetsBound();
@@ -491,8 +462,6 @@
mViewPager.snapToPage(AdapterHolder.PRIMARY);
}
attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
-
- mSearchAndRecommendationsScrollController.updateMarginAndPadding();
}
@Override
@@ -505,10 +474,10 @@
private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
mIsInSearchMode = isInSearchMode;
if (isInSearchMode) {
- mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.setVisibility(GONE);
+ mSearchScrollController.mRecommendedWidgetsTable.setVisibility(GONE);
if (mHasWorkProfile) {
mViewPager.setVisibility(GONE);
- mTabsView.setVisibility(GONE);
+ mSearchScrollController.mTabBar.setVisibility(GONE);
} else {
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.setVisibility(GONE);
}
@@ -536,8 +505,7 @@
}
List<WidgetItem> recommendedWidgets =
mActivityContext.getPopupDataProvider().getRecommendedWidgets();
- WidgetsRecommendationTableLayout table =
- mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
+ WidgetsRecommendationTableLayout table = mSearchScrollController.mRecommendedWidgetsTable;
if (recommendedWidgets.size() > 0) {
float noWidgetsViewHeight = 0;
if (mIsNoWidgetsViewNeeded) {
@@ -554,7 +522,7 @@
makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
MeasureSpec.EXACTLY));
float maxTableHeight = (mContent.getMeasuredHeight()
- - mTabsHeight - mViewPagerTopPadding - getHeaderViewHeight()
+ - mTabsHeight - getHeaderViewHeight()
- noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
@@ -617,10 +585,10 @@
mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
}
- if (mSearchAndRecommendationViewHolder.mSearchBar.isSearchBarFocused()
+ if (mSearchScrollController.mSearchBar.isSearchBarFocused()
&& !getPopupContainer().isEventOverView(
- mSearchAndRecommendationViewHolder.mSearchBarContainer, ev)) {
- mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
+ mSearchScrollController.mSearchBarContainer, ev)) {
+ mSearchScrollController.mSearchBar.clearSearchBarFocus();
}
}
return super.onControllerInterceptTouchEvent(ev);
@@ -661,10 +629,8 @@
@Override
public int getHeaderViewHeight() {
- return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
- + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
- + measureHeightWithVerticalMargins(
- (View) mSearchAndRecommendationViewHolder.mSearchBarContainer);
+ return measureHeightWithVerticalMargins(mSearchScrollController.mHeaderTitle)
+ + measureHeightWithVerticalMargins(mSearchScrollController.mSearchBarContainer);
}
/** private the height, in pixel, + the vertical margins of a given view. */
@@ -681,14 +647,14 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mIsInSearchMode) {
- mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ mSearchScrollController.mSearchBar.reset();
}
}
@Override
public boolean onBackPressed() {
if (mIsInSearchMode) {
- mSearchAndRecommendationViewHolder.mSearchBar.reset();
+ mSearchScrollController.mSearchBar.reset();
return true;
}
return super.onBackPressed();
@@ -701,11 +667,10 @@
}
@Nullable private View getViewToShowEducationTip() {
- if (mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getVisibility() == VISIBLE
- && mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable.getChildCount() > 0
- ) {
- return ((ViewGroup) mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
- .getChildAt(0)).getChildAt(0);
+ if (mSearchScrollController.mRecommendedWidgetsTable.getVisibility() == VISIBLE
+ && mSearchScrollController.mRecommendedWidgetsTable.getChildCount() > 0) {
+ return ((ViewGroup) mSearchScrollController.mRecommendedWidgetsTable.getChildAt(0))
+ .getChildAt(0);
}
AdapterHolder adapterHolder = mAdapters.get(mIsInSearchMode
@@ -722,7 +687,7 @@
.findFirst()
.orElse(null);
if (viewHolderForTip != null) {
- return ((ViewGroup) viewHolderForTip.mTableContainer.getChildAt(0)).getChildAt(0);
+ return ((ViewGroup) viewHolderForTip.tableContainer.getChildAt(0)).getChildAt(0);
}
return null;
@@ -780,8 +745,8 @@
mWidgetsListAdapter = new WidgetsListAdapter(
context,
LayoutInflater.from(context),
- apps.getWidgetCache(),
apps.getIconCache(),
+ this::getEmptySpaceHeight,
/* iconClickListener= */ WidgetsFullSheet.this,
/* iconLongClickListener= */ WidgetsFullSheet.this);
mWidgetsListAdapter.setHasStableIds(true);
@@ -801,46 +766,24 @@
mWidgetsListItemAnimator.setSupportsChangeAnimations(false);
}
+ private int getEmptySpaceHeight() {
+ return mSearchScrollController.getHeaderHeight();
+ }
+
void setup(WidgetsRecyclerView recyclerView) {
mWidgetsRecyclerView = recyclerView;
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
mWidgetsRecyclerView.setEdgeEffectFactory(
- ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
+ ((SpringRelativeLayout) mContent).createEdgeEffectFactory());
// Recycler view binds to fast scroller when it is attached to screen. Make sure
// search recycler view is bound to fast scroller if user is in search mode at the time
// of attachment.
if (mAdapterType == PRIMARY || mAdapterType == WORK) {
mWidgetsRecyclerView.addOnAttachStateChangeListener(mBindScrollbarInSearchMode);
}
- mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
}
}
-
- final class SearchAndRecommendationViewHolder {
- final SearchAndRecommendationsView mContainer;
- final View mCollapseHandle;
- final View mSearchBarContainer;
- final WidgetsSearchBar mSearchBar;
- final TextView mHeaderTitle;
- final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
-
- SearchAndRecommendationViewHolder(
- SearchAndRecommendationsView searchAndRecommendationContainer) {
- mContainer = searchAndRecommendationContainer;
- mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
- mSearchBarContainer = mContainer.findViewById(R.id.search_bar_container);
- mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
- mHeaderTitle = mContainer.findViewById(R.id.title);
- mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
- mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
- getRecyclerView().onTouchEvent(event);
- return false;
- });
- mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
- mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
- }
- }
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 1125b82..d52134c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -16,19 +16,20 @@
package com.android.launcher3.widget.picker;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_APP_EXPANDED;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_DEFAULT;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_FIRST;
+import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_LAST;
import android.content.Context;
import android.graphics.Rect;
import android.os.Process;
import android.util.Log;
-import android.util.Size;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import android.widget.TableRow;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -38,32 +39,27 @@
import androidx.recyclerview.widget.RecyclerView.LayoutParams;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.LabelComparator;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
-import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.util.WidgetSizes;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
+import java.util.function.IntSupplier;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -84,20 +80,18 @@
private static final boolean DEBUG = false;
/** Uniquely identifies widgets list view type within the app. */
+ private static final int VIEW_TYPE_WIDGETS_SPACE = R.id.view_type_widgets_space;
private static final int VIEW_TYPE_WIDGETS_LIST = R.id.view_type_widgets_list;
private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
private final Context mContext;
- private final Launcher mLauncher;
- private final CachingWidgetPreviewLoader mCachingPreviewLoader;
private final WidgetsDiffReporter mDiffReporter;
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
- private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
private final WidgetListBaseRowEntryComparator mRowComparator =
new WidgetListBaseRowEntryComparator();
- private List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
+ private final List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
@Nullable private PackageUserKey mWidgetsContentVisiblePackageUserKey = null;
@@ -109,42 +103,36 @@
@Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
@Nullable private RecyclerView mRecyclerView;
@Nullable private PackageUserKey mPendingClickHeader;
- private final int mShortcutPreviewPadding;
private final int mSpacingBetweenEntries;
private int mMaxSpanSize = 4;
- private final WidgetPreviewLoadedCallback mPreviewLoadedCallback =
- ignored -> updateVisibleEntries();
-
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
- DatabaseWidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
+ IconCache iconCache, IntSupplier emptySpaceHeightProvider,
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
mContext = context;
- mLauncher = Launcher.getLauncher(context);
- mCachingPreviewLoader = new CachingWidgetPreviewLoader(widgetPreviewLoader);
mDiffReporter = new WidgetsDiffReporter(iconCache, this);
WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
- mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
- layoutInflater, iconClickListener, iconLongClickListener,
- mCachingPreviewLoader, listDrawableFactory, /* listAdapter= */ this);
- mViewHolderBinders.put(VIEW_TYPE_WIDGETS_LIST, mWidgetsListTableViewHolderBinder);
+
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_LIST,
+ new WidgetsListTableViewHolderBinder(
+ layoutInflater, iconClickListener, iconLongClickListener,
+ listDrawableFactory));
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_HEADER,
new WidgetsListHeaderViewHolderBinder(
layoutInflater,
/* onHeaderClickListener= */ this,
- listDrawableFactory,
- /* listAdapter= */ this));
+ listDrawableFactory));
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_SEARCH_HEADER,
new WidgetsListSearchHeaderViewHolderBinder(
layoutInflater,
/* onHeaderClickListener= */ this,
- listDrawableFactory,
- /* listAdapter= */ this));
- mShortcutPreviewPadding =
- 2 * context.getResources()
- .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+ listDrawableFactory));
+ mViewHolderBinders.put(
+ VIEW_TYPE_WIDGETS_SPACE,
+ new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
mSpacingBetweenEntries =
context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
}
@@ -178,33 +166,19 @@
mFilter = filter;
}
- /**
- * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}.
- *
- * @see WidgetCell#setApplyBitmapDeferred(boolean)
- */
- public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
- mWidgetsListTableViewHolderBinder.setApplyBitmapDeferred(isDeferred);
-
- for (int i = rv.getChildCount() - 1; i >= 0; i--) {
- ViewHolder viewHolder = rv.getChildViewHolder(rv.getChildAt(i));
- if (viewHolder.getItemViewType() == VIEW_TYPE_WIDGETS_LIST) {
- WidgetsRowViewHolder holder = (WidgetsRowViewHolder) viewHolder;
- for (int j = holder.mTableContainer.getChildCount() - 1; j >= 0; j--) {
- TableRow row = (TableRow) holder.mTableContainer.getChildAt(j);
- for (int k = row.getChildCount() - 1; k >= 0; k--) {
- ((WidgetCell) row.getChildAt(k)).setApplyBitmapDeferred(isDeferred);
- }
- }
- }
- }
- }
-
@Override
public int getItemCount() {
return mVisibleEntries.size();
}
+ /**
+ * Returns true if the adapter has entries which will be visible to the user
+ */
+ public boolean hasVisibleEntries() {
+ // Account for the 1st space entry
+ return getItemCount() > 1;
+ }
+
/** Returns all items that will be drawn in a recycler view. */
public List<WidgetsListBaseEntry> getItems() {
return mVisibleEntries;
@@ -217,9 +191,9 @@
/** Updates the widget list based on {@code tempEntries}. */
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
- mCachingPreviewLoader.clearAll();
- mAllEntries = tempEntries.stream().sorted(mRowComparator)
- .collect(Collectors.toList());
+ mAllEntries.clear();
+ mAllEntries.add(new WidgetListSpaceEntry());
+ tempEntries.stream().sorted(mRowComparator).forEach(mAllEntries::add);
if (shouldClearVisibleEntries()) {
mVisibleEntries.clear();
}
@@ -230,15 +204,10 @@
public void setWidgetsOnSearch(List<WidgetsListBaseEntry> searchResults) {
// Forget the expanded package every time widget list is refreshed in search mode.
mWidgetsContentVisiblePackageUserKey = null;
- cancelLoadingPreviews();
setWidgets(searchResults);
}
private void updateVisibleEntries() {
- // If not all previews are ready, then defer this update and try again after the preview
- // loads.
- if (!ensureAllPreviewsReady()) return;
-
// Get the current top of the header with the matching key before adjusting the visible
// entries.
OptionalInt previousPositionForPackageUserKey =
@@ -247,8 +216,9 @@
getOffsetForPosition(previousPositionForPackageUserKey);
List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
- .filter(entry -> (mFilter == null || mFilter.test(entry))
+ .filter(entry -> ((mFilter == null || mFilter.test(entry))
&& mHeaderAndSelectedContentFilter.test(entry))
+ || entry instanceof WidgetListSpaceEntry)
.map(entry -> {
if (entry instanceof WidgetsListBaseEntry.Header<?>
&& matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
@@ -275,54 +245,6 @@
}
}
- /**
- * Checks that all preview images are loaded and starts loading for those that aren't ready.
- *
- * @return true if all previews are ready and the data can be updated, false otherwise.
- */
- private boolean ensureAllPreviewsReady() {
- boolean allReady = true;
- BaseActivity activity = BaseActivity.fromContext(mContext);
- for (WidgetsListBaseEntry entry : mAllEntries) {
- if (!(entry instanceof WidgetsListContentEntry)) continue;
-
- WidgetsListContentEntry contentEntry = (WidgetsListContentEntry) entry;
- if (!matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
- // If the entry isn't visible, clear any loaded previews.
- mCachingPreviewLoader.clearPreviews(contentEntry.mWidgets);
- continue;
- }
-
- for (int i = 0; i < entry.mWidgets.size(); i++) {
- WidgetItem widgetItem = entry.mWidgets.get(i);
- DeviceProfile deviceProfile = activity.getDeviceProfile();
- Size widgetSize = WidgetSizes.getWidgetItemSizePx(mContext, deviceProfile,
- widgetItem);
- if (widgetItem.isShortcut()) {
- widgetSize =
- new Size(
- widgetSize.getWidth() + mShortcutPreviewPadding,
- widgetSize.getHeight() + mShortcutPreviewPadding);
- }
-
- if (widgetItem.hasPreviewLayout()
- || mCachingPreviewLoader.isPreviewLoaded(widgetItem, widgetSize)) {
- // The widget is ready if it can be rendered with a preview layout or if its
- // preview bitmap is in the cache.
- continue;
- }
-
- // If we've reached this point, we should load the preview for the widget.
- allReady = false;
- mCachingPreviewLoader.loadPreview(
- activity,
- widgetItem,
- widgetSize,
- mPreviewLoadedCallback);
- }
- }
- return allReady;
- }
/** Returns whether {@code entry} matches {@code key}. */
private static boolean isHeaderForPackageUserKey(
@@ -343,16 +265,26 @@
public void resetExpandedHeader() {
if (mWidgetsContentVisiblePackageUserKey != null) {
mWidgetsContentVisiblePackageUserKey = null;
- cancelLoadingPreviews();
updateVisibleEntries();
}
}
@Override
- public void onBindViewHolder(ViewHolder holder, int pos) {
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ onBindViewHolder(holder, position, Collections.EMPTY_LIST);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int pos, List<Object> payloads) {
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
- viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
+
+ // The first entry has an empty space, count from second entries.
+ int listPos = (pos > 1) ? POSITION_DEFAULT : POSITION_FIRST;
+ if (pos == (getItemCount() - 1)) {
+ listPos |= POSITION_LAST;
+ }
+ viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos, payloads);
holder.itemView.setTag(R.id.tag_widget_entry, entry);
}
@@ -395,6 +327,8 @@
return VIEW_TYPE_WIDGETS_HEADER;
} else if (entry instanceof WidgetsListSearchHeaderEntry) {
return VIEW_TYPE_WIDGETS_SEARCH_HEADER;
+ } else if (entry instanceof WidgetListSpaceEntry) {
+ return VIEW_TYPE_WIDGETS_SPACE;
}
throw new UnsupportedOperationException("ViewHolderBinder not found for " + entry);
}
@@ -404,11 +338,10 @@
// Ignore invalid clicks, such as collapsing a package that isn't currently expanded.
if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return;
- cancelLoadingPreviews();
-
if (showWidgets) {
mWidgetsContentVisiblePackageUserKey = packageUserKey;
- mLauncher.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED);
+ ActivityContext.lookupContext(mContext)
+ .getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED);
} else {
mWidgetsContentVisiblePackageUserKey = null;
}
@@ -420,16 +353,6 @@
updateVisibleEntries();
}
- private void cancelLoadingPreviews() {
- mCachingPreviewLoader.clearAll();
- }
-
- /** Returns the position of the currently expanded header, or empty if it's not present. */
- public OptionalInt getSelectedHeaderPosition() {
- if (mWidgetsContentVisiblePackageUserKey == null) return OptionalInt.empty();
- return getPositionForPackageUserKey(mWidgetsContentVisiblePackageUserKey);
- }
-
/**
* Returns the position of {@code key} in {@link #mVisibleEntries}, or empty if it's not
* present.
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index 2f8f1ba..fadb637 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -23,6 +23,8 @@
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import java.util.List;
+
/**
* Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
*/
@@ -31,16 +33,13 @@
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
OnHeaderClickListener onHeaderClickListener,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
@Override
@@ -53,14 +52,14 @@
@Override
public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
- int position) {
+ @ListPosition int position, List<Object> payloads) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setListDrawableState(
WidgetsListDrawableState.obtain(
- /* isFirst= */ position == 0,
- /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+ (position & POSITION_FIRST) != 0,
+ (position & POSITION_LAST) != 0,
/* isExpanded= */ data.isWidgetListShown()));
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
mOnHeaderClickListener.onHeaderClicked(
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java b/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java
deleted file mode 100644
index 2b7f544..0000000
--- a/src/com/android/launcher3/widget/picker/WidgetsListLayoutManager.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget.picker;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.launcher3.widget.picker.SearchAndRecommendationsScrollController.OnContentChangeListener;
-
-/**
- * A layout manager for the {@link WidgetsRecyclerView}.
- *
- * {@link #setOnContentChangeListener(OnContentChangeListener)} can be used to register a callback
- * for when the content of the layout manager has changed, following measurement and animation.
- */
-public final class WidgetsListLayoutManager extends LinearLayoutManager {
- @Nullable
- private OnContentChangeListener mOnContentChangeListener;
-
- public WidgetsListLayoutManager(Context context) {
- super(context);
- }
-
- @Override
- public void onLayoutCompleted(RecyclerView.State state) {
- super.onLayoutCompleted(state);
- if (mOnContentChangeListener != null) {
- mOnContentChangeListener.onContentChanged();
- }
- }
-
- public void setOnContentChangeListener(@Nullable OnContentChangeListener listener) {
- mOnContentChangeListener = listener;
- }
-}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
index 31dd9ee..bff43c1 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -24,6 +24,8 @@
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+import java.util.List;
+
/**
* Binds data from {@link WidgetsListHeaderEntry} to UI elements in {@link WidgetsListHeaderHolder}.
*/
@@ -32,16 +34,13 @@
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final WidgetsListAdapter mWidgetsListAdapter;
public WidgetsListSearchHeaderViewHolderBinder(LayoutInflater layoutInflater,
OnHeaderClickListener onHeaderClickListener,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
}
@Override
@@ -54,14 +53,14 @@
@Override
public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
- WidgetsListSearchHeaderEntry data, int position) {
+ WidgetsListSearchHeaderEntry data, @ListPosition int position, List<Object> payloads) {
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
widgetsListHeader.applyFromItemInfoWithIcon(data);
widgetsListHeader.setExpanded(data.isWidgetListShown());
widgetsListHeader.setListDrawableState(
WidgetsListDrawableState.obtain(
- /* isFirst= */ position == 0,
- /* isLast= */ position == mWidgetsListAdapter.getItemCount() - 1,
+ (position & POSITION_FIRST) != 0,
+ (position & POSITION_LAST) != 0,
/* isExpanded= */ data.isWidgetListShown()));
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
mOnHeaderClickListener.onHeaderClicked(isExpanded,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 9c06558..8c9ff09 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -20,7 +20,7 @@
import android.graphics.Bitmap;
import android.util.Log;
-import android.util.Size;
+import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,7 +33,6 @@
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.recyclerview.ViewHolderBinder;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.util.WidgetsTableUtils;
@@ -53,32 +52,16 @@
private final OnClickListener mIconClickListener;
private final OnLongClickListener mIconLongClickListener;
private final WidgetsListDrawableFactory mListDrawableFactory;
- private final CachingWidgetPreviewLoader mWidgetPreviewLoader;
- private final WidgetsListAdapter mWidgetsListAdapter;
- private boolean mApplyBitmapDeferred = false;
public WidgetsListTableViewHolderBinder(
LayoutInflater layoutInflater,
OnClickListener iconClickListener,
OnLongClickListener iconLongClickListener,
- CachingWidgetPreviewLoader widgetPreviewLoader,
- WidgetsListDrawableFactory listDrawableFactory,
- WidgetsListAdapter listAdapter) {
+ WidgetsListDrawableFactory listDrawableFactory) {
mLayoutInflater = layoutInflater;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
- mWidgetPreviewLoader = widgetPreviewLoader;
mListDrawableFactory = listDrawableFactory;
- mWidgetsListAdapter = listAdapter;
- }
-
- /**
- * Defers applying bitmap on all the {@link WidgetCell} at
- * {@link #bindViewHolder(WidgetsRowViewHolder, WidgetsListContentEntry, int)} if
- * {@code applyBitmapDeferred} is {@code true}.
- */
- public void setApplyBitmapDeferred(boolean applyBitmapDeferred) {
- mApplyBitmapDeferred = applyBitmapDeferred;
}
@Override
@@ -90,27 +73,30 @@
WidgetsRowViewHolder viewHolder =
new WidgetsRowViewHolder(mLayoutInflater.inflate(
R.layout.widgets_table_container, parent, false));
- viewHolder.mTableContainer.setBackgroundDrawable(
+ viewHolder.tableContainer.setBackgroundDrawable(
mListDrawableFactory.createContentBackgroundDrawable());
return viewHolder;
}
@Override
public void bindViewHolder(WidgetsRowViewHolder holder, WidgetsListContentEntry entry,
- int position) {
- WidgetsListTableView table = holder.mTableContainer;
+ @ListPosition int position, List<Object> payloads) {
+ for (Object payload : payloads) {
+ Pair<WidgetItem, Bitmap> pair = (Pair) payload;
+ holder.previewCache.put(pair.first, pair.second);
+ }
+
+ WidgetsListTableView table = holder.tableContainer;
if (DEBUG) {
Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
entry.mWidgets.size(), table.getChildCount()));
}
-
- table.setListDrawableState(
- position == mWidgetsListAdapter.getItemCount() - 1 ? LAST : MIDDLE);
-
+ table.setListDrawableState(((position & POSITION_LAST) != 0) ? LAST : MIDDLE);
List<ArrayList<WidgetItem>> widgetItemsTable =
WidgetsTableUtils.groupWidgetItemsIntoTable(
entry.mWidgets, entry.getMaxSpanSizeInCells());
recycleTableBeforeBinding(table, widgetItemsTable);
+
// Bind the widget items.
for (int i = 0; i < widgetItemsTable.size(); i++) {
List<WidgetItem> widgetItemsPerRow = widgetItemsTable.get(i);
@@ -120,16 +106,17 @@
WidgetCell widget = (WidgetCell) row.getChildAt(j);
widget.clear();
WidgetItem widgetItem = widgetItemsPerRow.get(j);
- Size previewSize = widget.setPreviewSize(widgetItem);
- widget.applyFromCellItem(widgetItem, mWidgetPreviewLoader);
- widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
- Bitmap preview = mWidgetPreviewLoader.getPreview(widgetItem, previewSize);
- if (preview == null) {
- widget.ensurePreview();
- } else {
- widget.applyPreview(preview);
- }
widget.setVisibility(View.VISIBLE);
+
+ // When preview loads, notify adapter to rebind the item and possibly animate
+ widget.applyFromCellItem(widgetItem, 1f,
+ bitmap -> {
+ if (holder.getBindingAdapter() != null) {
+ holder.getBindingAdapter().notifyItemChanged(
+ holder.getBindingAdapterPosition(),
+ Pair.create(widgetItem, bitmap));
+ }
+ }, holder.previewCache.get(widgetItem));
}
}
}
@@ -170,6 +157,7 @@
View preview = widget.findViewById(R.id.widget_preview_container);
preview.setOnClickListener(mIconClickListener);
preview.setOnLongClickListener(mIconLongClickListener);
+ widget.setAnimatePreview(false);
tableRow.addView(widget);
}
}
@@ -178,9 +166,10 @@
@Override
public void unbindViewHolder(WidgetsRowViewHolder holder) {
- int numOfRows = holder.mTableContainer.getChildCount();
+ int numOfRows = holder.tableContainer.getChildCount();
+ holder.previewCache.clear();
for (int i = 0; i < numOfRows; i++) {
- TableRow tableRow = (TableRow) holder.mTableContainer.getChildAt(i);
+ TableRow tableRow = (TableRow) holder.tableContainer.getChildAt(i);
int numOfCols = tableRow.getChildCount();
for (int j = 0; j < numOfCols; j++) {
WidgetCell widget = (WidgetCell) tableRow.getChildAt(j);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 0b8ca34..c986007 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -32,7 +32,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.WidgetCell;
@@ -53,7 +52,6 @@
private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
@Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
@Nullable private OnClickListener mWidgetCellOnClickListener;
- @Nullable private OnTouchListener mWidgetCellOnTouchListener;
public WidgetsRecommendationTableLayout(Context context) {
this(context, /* attrs= */ null);
@@ -79,11 +77,6 @@
mWidgetCellOnClickListener = widgetCellOnClickListener;
}
- /** Sets a {@link android.view.View.OnTouchListener} for all widget cells in this table. */
- public void setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener) {
- mWidgetCellOnTouchListener = widgetCellOnTouchListener;
- }
-
/**
* Sets a list of recommended widgets that would like to be displayed in this table within the
* desired {@code recommendationTableMaxHeight}.
@@ -115,10 +108,7 @@
for (WidgetItem widgetItem : widgetItems) {
WidgetCell widgetCell = addItemCell(tableRow);
- widgetCell.setPreviewSize(widgetItem, data.mPreviewScale);
- widgetCell.applyFromCellItem(widgetItem,
- LauncherAppState.getInstance(getContext()).getWidgetCache());
- widgetCell.ensurePreview();
+ widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
}
addView(tableRow);
}
@@ -129,7 +119,6 @@
WidgetCell widget = (WidgetCell) LayoutInflater.from(
getContext()).inflate(R.layout.widget_cell, parent, false);
- widget.setOnTouchListener(mWidgetCellOnTouchListener);
View previewContainer = widget.findViewById(R.id.widget_preview_container);
previewContainer.setOnClickListener(mWidgetCellOnClickListener);
previewContainer.setOnLongClickListener(mWidgetCellOnLongClickListener);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 7671841..f780f03 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -23,7 +23,6 @@
import android.view.View;
import android.widget.TableLayout;
-import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
@@ -32,11 +31,12 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.picker.SearchAndRecommendationsScrollController.OnContentChangeListener;
+import com.android.launcher3.widget.picker.WidgetsSpaceViewHolderBinder.EmptySpaceView;
/**
* The widgets recycler view.
@@ -50,10 +50,13 @@
private final Point mFastScrollerOffset = new Point();
private boolean mTouchDownOnScroller;
private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
+
+ // Cached sizes
private int mLastVisibleWidgetContentTableHeight = 0;
private int mWidgetHeaderHeight = 0;
+ private int mWidgetEmptySpaceHeight = 0;
+
private final int mSpacingBetweenEntries;
- @Nullable private OnContentChangeListener mOnContentChangeListener;
public WidgetsRecyclerView(Context context) {
this(context, null);
@@ -82,9 +85,7 @@
super.onFinishInflate();
// create a layout manager with Launcher's context so that scroll position
// can be preserved during screen rotation.
- WidgetsListLayoutManager layoutManager = new WidgetsListLayoutManager(getContext());
- layoutManager.setOnContentChangeListener(mOnContentChangeListener);
- setLayoutManager(layoutManager);
+ setLayoutManager(new LinearLayoutManager(getContext()));
}
@Override
@@ -169,10 +170,12 @@
// This assumes there is ever only one content shown in this recycler view.
mLastVisibleWidgetContentTableHeight = view.getMeasuredHeight();
} else if (view instanceof WidgetsListHeader
- && mLastVisibleWidgetContentTableHeight == 0
+ && mWidgetHeaderHeight == 0
&& view.getMeasuredHeight() > 0) {
// This assumes all header views are of the same height.
mWidgetHeaderHeight = view.getMeasuredHeight();
+ } else if (view instanceof EmptySpaceView && view.getMeasuredHeight() > 0) {
+ mWidgetEmptySpaceHeight = view.getMeasuredHeight();
}
}
@@ -251,14 +254,6 @@
scrollToPosition(0);
}
- public void setOnContentChangeListener(@Nullable OnContentChangeListener listener) {
- mOnContentChangeListener = listener;
- WidgetsListLayoutManager layoutManager = (WidgetsListLayoutManager) getLayoutManager();
- if (layoutManager != null) {
- layoutManager.setOnContentChangeListener(listener);
- }
- }
-
/**
* Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
* {@code untilIndex}.
@@ -283,6 +278,8 @@
}
} else if (entry instanceof WidgetsListContentEntry) {
totalItemsHeight += mLastVisibleWidgetContentTableHeight;
+ } else if (entry instanceof WidgetListSpaceEntry) {
+ totalItemsHeight += mWidgetEmptySpaceHeight;
} else {
throw new UnsupportedOperationException("Can't estimate height for " + entry);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
index 618e2cb..fe2d84b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRowViewHolder.java
@@ -15,20 +15,26 @@
*/
package com.android.launcher3.widget.picker;
+import android.graphics.Bitmap;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+
+import java.util.HashMap;
+import java.util.Map;
/** A {@link ViewHolder} for showing widgets of an app in the full widget picker. */
public final class WidgetsRowViewHolder extends ViewHolder {
- public final WidgetsListTableView mTableContainer;
+ public final WidgetsListTableView tableContainer;
+ public final Map<WidgetItem, Bitmap> previewCache = new HashMap<>();
public WidgetsRowViewHolder(View v) {
super(v);
- mTableContainer = v.findViewById(R.id.widgets_table);
+ tableContainer = v.findViewById(R.id.widgets_table);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
new file mode 100644
index 0000000..1aa5753
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsSpaceViewHolderBinder.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget.picker;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.widget.model.WidgetListSpaceEntry;
+
+import java.util.List;
+import java.util.function.IntSupplier;
+
+/**
+ * {@link ViewHolderBinder} for binding the top empty space
+ */
+public class WidgetsSpaceViewHolderBinder
+ implements ViewHolderBinder<WidgetListSpaceEntry, ViewHolder> {
+
+ private final IntSupplier mEmptySpaceHeightProvider;
+
+ public WidgetsSpaceViewHolderBinder(IntSupplier emptySpaceHeightProvider) {
+ mEmptySpaceHeightProvider = emptySpaceHeightProvider;
+ }
+
+ @Override
+ public ViewHolder newViewHolder(ViewGroup parent) {
+ return new ViewHolder(new EmptySpaceView(parent.getContext())) { };
+ }
+
+ @Override
+ public void bindViewHolder(ViewHolder holder, WidgetListSpaceEntry data,
+ @ListPosition int position, List<Object> payloads) {
+ ((EmptySpaceView) holder.itemView).setFixedHeight(mEmptySpaceHeightProvider.getAsInt());
+ }
+
+ /**
+ * Empty view which allows listening for 'Y' changes
+ */
+ public static class EmptySpaceView extends View {
+
+ private Runnable mOnYChangeCallback;
+ private int mHeight = 0;
+
+ private EmptySpaceView(Context context) {
+ super(context);
+ animate().setUpdateListener(v -> notifyYChanged());
+ }
+
+ /**
+ * Sets the height for the empty view
+ * @return true if the height changed, false otherwise
+ */
+ public boolean setFixedHeight(int height) {
+ if (mHeight != height) {
+ mHeight = height;
+ requestLayout();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, makeMeasureSpec(mHeight, EXACTLY));
+ }
+
+ public void setOnYChangeCallback(Runnable callback) {
+ mOnYChangeCallback = callback;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ notifyYChanged();
+ }
+
+ @Override
+ public void offsetTopAndBottom(int offset) {
+ super.offsetTopAndBottom(offset);
+ notifyYChanged();
+ }
+
+ @Override
+ public void setTranslationY(float translationY) {
+ super.setTranslationY(translationY);
+ notifyYChanged();
+ }
+
+ private void notifyYChanged() {
+ if (mOnYChangeCallback != null) {
+ mOnYChangeCallback.run();
+ }
+ }
+ }
+}
diff --git a/src_plugins/com/android/systemui/plugins/OneSearch.java b/src_plugins/com/android/systemui/plugins/OneSearch.java
new file mode 100644
index 0000000..6d57c19
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OneSearch.java
@@ -0,0 +1,59 @@
+/*
+ * 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.systemui.plugins;
+
+import android.graphics.Bitmap;
+import android.text.Spanned;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.util.ArrayList;
+
+/**
+ * Implement this interface to get suggest for one search.
+ */
+@ProvidesInterface(action = OneSearch.ACTION, version = OneSearch.VERSION)
+public interface OneSearch extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_ONE_SEARCH";
+ int VERSION = 2;
+
+ /**
+ * Get the content provider warmed up.
+ */
+ void warmUp();
+
+ /**
+ * Get the suggests for the query.
+ * @param query The query to get the search suggests for.
+ */
+ ArrayList<Spanned> getSuggests(String query);
+
+ /**
+ * Get the image bitmap for the suggest.
+ * @param suggest The suggest to get the image bitmap for.
+ */
+ Bitmap getImageBitmap(Spanned suggest);
+
+ /**
+ * Get the subtitle for the suggest.
+ * @param suggest The suggest to get the subtitle for.
+ */
+ String getSubtitle(Spanned suggest);
+
+ /** Clear any cached data or storage used in search. */
+ void clear();
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 631067b..12e9e1e 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -150,7 +150,6 @@
}
}
- app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser);
return updatedItems;
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index cc90e6c..81e3f98 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -39,6 +39,13 @@
}
/**
+ * Returns a unique ID representing the display
+ */
+ public static String getUniqueId(Display display) {
+ return Integer.toString(display.getDisplayId());
+ }
+
+ /**
* Returns the minimum space that should be left empty at the end of hotseat
*/
public static int getHotseatEndOffset(Context context) {
diff --git a/tests/Android.bp b/tests/Android.bp
index da55c28..aeddc4c 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -20,7 +20,78 @@
default_applicable_licenses: ["packages_apps_Launcher3_license"],
}
+// Source code used for test
filegroup {
- name: "launcher3-test-src-common",
- srcs: ["src_common/**/*.java"],
+ name: "launcher-tests-src",
+ srcs: ["src/**/*.java"],
+}
+
+// Source code used for oop test helpers
+filegroup {
+ name: "launcher-oop-tests-src",
+ srcs: [
+ "src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
+ "src/com/android/launcher3/ui/ActivityLeakTracker.java",
+ "src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
+ "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/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/ui/TaplTestsLauncher3.java",
+ "src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
+ "src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
+ "src/com/android/launcher3/testcomponent/TestCommandReceiver.java",
+ "src/com/android/launcher3/testcomponent/TestLauncherActivity.java",
+ ],
+}
+
+// Library with all the dependencies for building quickstep
+android_library {
+ name: "Launcher3TestLib",
+ srcs: [ ],
+ resource_dirs: ["res"],
+ static_libs: [
+ "launcher-aosp-tapl",
+ "androidx.test.core",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "androidx.test.espresso.core",
+ "androidx.test.espresso.contrib",
+ "androidx.test.espresso.intents",
+ "androidx.test.uiautomator_uiautomator",
+ "mockito-target-inline-minus-junit4",
+ "launcher_log_protos_lite",
+ "truth-prebuilt"
+ ],
+ manifest: "AndroidManifest-common.xml",
+ platform_apis: true,
+}
+
+android_test {
+ name: "Launcher3Tests",
+ srcs: [
+ ":launcher-tests-src",
+ ],
+ static_libs: ["Launcher3TestLib"],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ "android.test.mock",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ use_embedded_native_libs: false,
+ compile_multilib: "both",
+ instrumentation_for: "Launcher3",
+ manifest: "AndroidManifest.xml",
+ platform_apis: true,
+ test_config: "Launcher3Tests.xml",
+ data: [":Launcher3"]
}
diff --git a/tests/Android.mk b/tests/Android.mk
deleted file mode 100644
index 6adc685..0000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright (C) 2015 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-#
-# Build rule for Launcher3Tests
-#
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.runner \
- androidx.test.rules \
- androidx.test.uiautomator_uiautomator \
- mockito-target-minus-junit4 \
- launcher_log_protos_lite
-
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_STATIC_JAVA_LIBRARIES += launcher-aosp-tapl
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, src_common)
-
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
-
-LOCAL_PACKAGE_NAME := Launcher3Tests
-
-LOCAL_INSTRUMENTATION_FOR := Launcher3
-
-LOCAL_TEST_CONFIG := Launcher3Tests.xml
-
-LOCAL_COMPATIBILITY_SUPPORT_FILES := $(call intermediates-dir-for,APPS,Launcher3)/package.apk:Launcher3.apk
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 918ec4a..8222f75 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -24,7 +24,7 @@
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
- <application android:debuggable="true">
+ <application android:debuggable="true" android:extractNativeLibs="true">
<uses-library android:name="android.test.runner"/>
<receiver
diff --git a/robolectric_tests/resources/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt
similarity index 100%
rename from robolectric_tests/resources/cache_data_updated_task_data.txt
rename to tests/res/raw/cache_data_updated_task_data.txt
diff --git a/robolectric_tests/resources/db_schema_v10.json b/tests/res/raw/db_schema_v10.json
similarity index 100%
rename from robolectric_tests/resources/db_schema_v10.json
rename to tests/res/raw/db_schema_v10.json
diff --git a/robolectric_tests/resources/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt
similarity index 100%
rename from robolectric_tests/resources/package_install_state_change_task_data.txt
rename to tests/res/raw/package_install_state_change_task_data.txt
diff --git a/robolectric_tests/resources/widgets_predication_update_task_data.txt b/tests/res/raw/widgets_predication_update_task_data.txt
similarity index 100%
rename from robolectric_tests/resources/widgets_predication_update_task_data.txt
rename to tests/res/raw/widgets_predication_update_task_data.txt
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
similarity index 90%
rename from robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
rename to tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index 2a94d9b..23e6235 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.folder;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -23,6 +25,9 @@
import android.content.Intent;
import android.os.UserHandle;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Executors;
@@ -30,15 +35,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
import java.util.ArrayList;
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class FolderNameProviderTest {
private Context mContext;
private WorkspaceItemInfo mItem1;
@@ -46,7 +47,7 @@
@Before
public void setUp() {
- mContext = RuntimeEnvironment.application;
+ mContext = getApplicationContext();
mItem1 = new WorkspaceItemInfo(new AppInfo(
new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
"title1",
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/tests/src/com/android/launcher3/logging/FileLogTest.java
similarity index 89%
rename from robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
rename to tests/src/com/android/launcher3/logging/FileLogTest.java
index 01b23ba..e5f8cec 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -1,16 +1,17 @@
package com.android.launcher3.logging;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
import java.io.File;
import java.io.PrintWriter;
@@ -20,8 +21,8 @@
/**
* Tests for {@link FileLog}
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class FileLogTest {
private File mTempDir;
@@ -29,7 +30,7 @@
public void setUp() {
int count = 0;
do {
- mTempDir = new File(RuntimeEnvironment.application.getCacheDir(),
+ mTempDir = new File(getApplicationContext().getCacheDir(),
"log-test-" + (count++));
} while (!mTempDir.mkdir());
diff --git a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
similarity index 84%
rename from robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
rename to tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index 8aa6f37..16f024e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -13,6 +13,9 @@
import android.graphics.Rect;
import android.util.Pair;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
@@ -21,19 +24,17 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.LauncherModelHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
import java.util.ArrayList;
import java.util.List;
@@ -41,8 +42,8 @@
/**
* Tests for {@link AddWorkspaceItemsTask}
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class AddWorkspaceItemsTaskTest {
private final ComponentName mComponent1 = new ComponentName("a", "b");
@@ -60,7 +61,7 @@
@Before
public void setup() {
mModelHelper = new LauncherModelHelper();
- mTargetContext = RuntimeEnvironment.application;
+ mTargetContext = mModelHelper.sandboxContext;
mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
mIdp.numColumns = mIdp.numRows = 5;
mAppState = LauncherAppState.getInstance(mTargetContext);
@@ -70,6 +71,11 @@
mNewScreens = new IntArray();
}
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
+ }
+
private AddWorkspaceItemsTask newTask(ItemInfo... items) {
List<Pair<ItemInfo, Object>> list = new ArrayList<>();
for (ItemInfo item : items) {
@@ -80,6 +86,8 @@
@Test
public void testFindSpaceForItem_prefers_second() throws Exception {
+ mIdp.isSplitDisplay = false;
+
// First screen has only one hole of size 1
int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
@@ -88,7 +96,7 @@
int[] spaceFound = newTask().findSpaceForItem(
mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
- assertEquals(2, spaceFound[0]);
+ assertEquals(1, spaceFound[0]);
assertTrue(mScreenOccupancy.get(spaceFound[0])
.isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
@@ -101,6 +109,24 @@
}
@Test
+ public void testFindSpaceForItem_prefers_third_on_split_display() throws Exception {
+ mIdp.isSplitDisplay = true;
+ // First screen has only one hole of size 1
+ int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ // Second screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ int[] spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
+ // For split display, it picks the next screen, even if there is enough space
+ // on previous screen
+ assertEquals(2, spaceFound[0]);
+ assertTrue(mScreenOccupancy.get(spaceFound[0])
+ .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
+ }
+
+ @Test
public void testFindSpaceForItem_adds_new_screen() throws Exception {
// First screen has 2 holes of sizes 3x2 and 2x3
setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
@@ -127,7 +153,7 @@
@Test
public void testAddItem_some_items_added() throws Exception {
Callbacks callbacks = mock(Callbacks.class);
- mModelHelper.getModel().addCallbacks(callbacks);
+ Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get();
WorkspaceItemInfo info = new WorkspaceItemInfo();
info.intent = new Intent().setComponent(mComponent1);
diff --git a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java b/tests/src/com/android/launcher3/model/BackupRestoreTest.java
similarity index 62%
rename from robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
rename to tests/src/com/android/launcher3/model/BackupRestoreTest.java
index a397db5..41914de 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
+++ b/tests/src/com/android/launcher3/model/BackupRestoreTest.java
@@ -17,6 +17,7 @@
package com.android.launcher3.model;
import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+import static android.os.Process.myUserHandle;
import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
@@ -26,74 +27,110 @@
import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
+import static com.android.launcher3.util.ReflectionHelpers.getField;
+import static com.android.launcher3.util.ReflectionHelpers.setField;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.robolectric.util.ReflectionHelpers.setField;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
import android.app.backup.BackupManager;
import android.content.pm.PackageInstaller;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.os.Process;
import android.os.UserHandle;
-import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.RestoreDbTask;
-import com.android.launcher3.shadows.LShadowBackupManager;
import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.SafeCloseable;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowUserManager;
/**
* Tests to verify backup and restore flow.
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(LooperMode.Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class BackupRestoreTest {
- private static final long MY_OLD_PROFILE_ID = 1;
- private static final long MY_PROFILE_ID = 0;
- private static final long OLD_WORK_PROFILE_ID = 11;
- private static final int WORK_PROFILE_ID = 10;
+ private static final int PER_USER_RANGE = 200000;
- private ShadowUserManager mUserManager;
+
+ private long mCurrentMyProfileId;
+ private long mOldMyProfileId;
+
+ private long mCurrentWorkProfileId;
+ private long mOldWorkProfileId;
+
private BackupManager mBackupManager;
private LauncherModelHelper mModelHelper;
private SQLiteDatabase mDb;
private InvariantDeviceProfile mIdp;
+ private UserHandle mWorkUserHandle;
+
+ private SafeCloseable mUserChangeListener;
+
@Before
public void setUp() {
+ mModelHelper = new LauncherModelHelper();
+
+ mCurrentMyProfileId = mModelHelper.defaultProfileId;
+ mOldMyProfileId = mCurrentMyProfileId + 1;
+ mCurrentWorkProfileId = mOldMyProfileId + 1;
+ mOldWorkProfileId = mCurrentWorkProfileId + 1;
+
+ mWorkUserHandle = UserHandle.getUserHandleForUid(PER_USER_RANGE);
+ mUserChangeListener = UserCache.INSTANCE.get(mModelHelper.sandboxContext)
+ .addUserChangeListener(() -> { });
+
setupUserManager();
setupBackupManager();
- mModelHelper = new LauncherModelHelper();
- RestoreDbTask.setPending(RuntimeEnvironment.application);
+ RestoreDbTask.setPending(mModelHelper.sandboxContext);
mDb = mModelHelper.provider.getDb();
- mIdp = InvariantDeviceProfile.INSTANCE.get(RuntimeEnvironment.application);
+ mIdp = InvariantDeviceProfile.INSTANCE.get(mModelHelper.sandboxContext);
+
+ }
+
+ @After
+ public void tearDown() {
+ mUserChangeListener.close();
+ mModelHelper.destroy();
}
private void setupUserManager() {
- final UserManager userManager = RuntimeEnvironment.application.getSystemService(
- UserManager.class);
- mUserManager = Shadow.extract(userManager);
- // sign in to work profile
- mUserManager.addUser(WORK_PROFILE_ID, "work", ShadowUserManager.FLAG_MANAGED_PROFILE);
+ UserCache cache = UserCache.INSTANCE.get(mModelHelper.sandboxContext);
+ synchronized (cache) {
+ LongSparseArray<UserHandle> users = getField(cache, "mUsers");
+ users.clear();
+ users.put(mCurrentMyProfileId, myUserHandle());
+ users.put(mCurrentWorkProfileId, mWorkUserHandle);
+
+ ArrayMap<UserHandle, Long> userMap = getField(cache, "mUserToSerialMap");
+ userMap.clear();
+ userMap.put(myUserHandle(), mCurrentMyProfileId);
+ userMap.put(mWorkUserHandle, mCurrentWorkProfileId);
+ }
}
private void setupBackupManager() {
- mBackupManager = new BackupManager(RuntimeEnvironment.application);
- final LShadowBackupManager bm = Shadow.extract(mBackupManager);
- bm.addProfile(MY_OLD_PROFILE_ID, Process.myUserHandle());
- bm.addProfile(OLD_WORK_PROFILE_ID, UserHandle.of(WORK_PROFILE_ID));
+ mBackupManager = spy(new BackupManager(mModelHelper.sandboxContext));
+ doReturn(myUserHandle()).when(mBackupManager)
+ .getUserForAncestralSerialNumber(eq(mOldMyProfileId));
+ doReturn(mWorkUserHandle).when(mBackupManager)
+ .getUserForAncestralSerialNumber(eq(mOldWorkProfileId));
}
@Test
@@ -118,18 +155,18 @@
{ SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
{ NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
{ APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
- }}, 1, MY_OLD_PROFILE_ID);
+ }}, 1, mOldMyProfileId);
// setup grid for work profile on second screen
mModelHelper.createGrid(new int[][][]{{
{ NO__ICON, APP_ICON, SHORTCUT, SHORTCUT},
{ SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
{ NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
{ APP_ICON, SHORTCUT, SHORTCUT, NO__ICON},
- }}, 2, OLD_WORK_PROFILE_ID);
+ }}, 2, mOldWorkProfileId);
// simulates the creation of backup upon restore
- new GridBackupTable(RuntimeEnvironment.application, mDb, mIdp.numDatabaseHotseatIcons,
+ new GridBackupTable(mModelHelper.sandboxContext, mDb, mIdp.numDatabaseHotseatIcons,
mIdp.numColumns, mIdp.numRows).doBackup(
- MY_OLD_PROFILE_ID, GridBackupTable.OPTION_REQUIRES_SANITIZATION);
+ mOldMyProfileId, GridBackupTable.OPTION_REQUIRES_SANITIZATION);
// reset favorites table
createTableUsingOldProfileId();
}
@@ -141,28 +178,28 @@
private void verifyTableIsFilled(String tableName, boolean sanitized) {
assertEquals(sanitized ? 12 : 13, getCount(mDb,
"SELECT * FROM " + tableName + " WHERE profileId = "
- + (sanitized ? MY_PROFILE_ID : MY_OLD_PROFILE_ID)));
+ + (sanitized ? mCurrentMyProfileId : mOldMyProfileId)));
assertEquals(10, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = "
- + (sanitized ? WORK_PROFILE_ID : OLD_WORK_PROFILE_ID)));
+ + (sanitized ? mCurrentWorkProfileId : mOldWorkProfileId)));
}
private void createTableUsingOldProfileId() {
// simulates the creation of favorites table on old device
dropTable(mDb, TABLE_NAME);
- addTableToDb(mDb, MY_OLD_PROFILE_ID, false);
+ addTableToDb(mDb, mOldMyProfileId, false);
}
private void createRestoreSession() throws Exception {
final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- final PackageInstaller installer = RuntimeEnvironment.application.getPackageManager()
+ final PackageInstaller installer = mModelHelper.sandboxContext.getPackageManager()
.getPackageInstaller();
final int sessionId = installer.createSession(params);
final PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
setField(info, "installReason", INSTALL_REASON_DEVICE_RESTORE);
// TODO: (b/148410677) we should verify the following call instead
// InstallSessionHelper.INSTANCE.get(getContext()).restoreDbIfApplicable(info);
- RestoreDbTask.restoreIfPossible(RuntimeEnvironment.application,
+ RestoreDbTask.restoreIfPossible(mModelHelper.sandboxContext,
mModelHelper.provider.getHelper(), mBackupManager);
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
similarity index 92%
rename from robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
rename to tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 9ac3fe7..dba0a40 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -16,6 +16,8 @@
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;
@@ -26,13 +28,10 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.LauncherModelHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
import java.util.Arrays;
import java.util.HashSet;
@@ -40,8 +39,8 @@
/**
* Tests for {@link CacheDataUpdatedTask}
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest {
private static final String NEW_LABEL_PREFIX = "new-label-";
@@ -51,10 +50,10 @@
@Before
public void setup() throws Exception {
mModelHelper = new LauncherModelHelper();
- mModelHelper.initializeData("/cache_data_updated_task_data.txt");
+ mModelHelper.initializeData("cache_data_updated_task_data");
// Add placeholder entries in the cache to simulate update
- Context context = RuntimeEnvironment.application;
+ Context context = mModelHelper.sandboxContext;
IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() {
@Override
@@ -86,6 +85,11 @@
}
}
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
+ }
+
private CacheDataUpdatedTask newTask(int op, String... pkg) {
return new CacheDataUpdatedTask(op, Process.myUserHandle(),
new HashSet<>(Arrays.asList(pkg)));
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
similarity index 91%
rename from robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
rename to tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index be03c7d..d849c8f 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -15,12 +15,13 @@
*/
package com.android.launcher3.model;
+import static androidx.test.InstrumentationRegistry.getContext;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -32,6 +33,10 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -40,15 +45,14 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.io.File;
/**
* Tests for {@link DbDowngradeHelper}
*/
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class DbDowngradeHelperTest {
private static final String SCHEMA_FILE = "test_schema.json";
@@ -60,7 +64,7 @@
@Before
public void setup() {
- mContext = RuntimeEnvironment.application;
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mSchemaFile = mContext.getFileStreamPath(SCHEMA_FILE);
mDbFile = mContext.getDatabasePath(DB_FILE);
}
@@ -77,8 +81,10 @@
public void testUpdateSchemaFile() throws Exception {
// Setup mock resources
Resources res = spy(mContext.getResources());
- doAnswer(i ->this.getClass().getResourceAsStream("/db_schema_v10.json"))
- .when(res).openRawResource(eq(R.raw.downgrade_schema));
+ Resources myRes = getContext().getResources();
+ doAnswer(i -> myRes.openRawResource(
+ myRes.getIdentifier("db_schema_v10", "raw", getContext().getPackageName())))
+ .when(res).openRawResource(R.raw.downgrade_schema);
Context context = spy(mContext);
when(context.getResources()).thenReturn(res);
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
similarity index 77%
rename from robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
rename to tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 655237d..004ed06 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -16,18 +16,18 @@
package com.android.launcher3.model;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
import static org.junit.Assert.assertEquals;
-import static org.robolectric.Shadows.shadowOf;
-import static org.robolectric.util.ReflectionHelpers.setField;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.model.data.FolderInfo;
@@ -35,19 +35,16 @@
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
/**
* Tests for layout parser for remote layout
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class DefaultLayoutProviderTest {
private LauncherModelHelper mModelHelper;
@@ -56,16 +53,18 @@
@Before
public void setUp() {
mModelHelper = new LauncherModelHelper();
- mTargetContext = RuntimeEnvironment.application;
+ mTargetContext = mModelHelper.sandboxContext;
+ }
- shadowOf(mTargetContext.getPackageManager())
- .addActivityIfNotPresent(new ComponentName(TEST_PACKAGE, TEST_PACKAGE));
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
}
@Test
public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
- .putApp(TEST_PACKAGE, TEST_PACKAGE));
+ .putApp(TEST_PACKAGE, TEST_ACTIVITY));
// Verify one item in hotseat
assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
@@ -77,9 +76,9 @@
@Test
public void testCustomProfileLoaded_with_folder() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
- .addApp(TEST_PACKAGE, TEST_PACKAGE)
- .addApp(TEST_PACKAGE, TEST_PACKAGE)
- .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY)
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY)
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY)
.build());
// Verify folder
@@ -92,9 +91,9 @@
@Test
public void testCustomProfileLoaded_with_folder_custom_title() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder("CustomFolder")
- .addApp(TEST_PACKAGE, TEST_PACKAGE)
- .addApp(TEST_PACKAGE, TEST_PACKAGE)
- .addApp(TEST_PACKAGE, TEST_PACKAGE)
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY)
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY)
+ .addApp(TEST_PACKAGE, TEST_ACTIVITY)
.build());
// Verify folder
@@ -112,12 +111,10 @@
// Add a placeholder session info so that the widget exists
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(pendingAppPkg);
+ params.setAppIcon(BitmapInfo.LOW_RES_ICON);
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
- int sessionId = installer.createSession(params);
- SessionInfo sessionInfo = installer.getSessionInfo(sessionId);
- setField(sessionInfo, "installerPackageName", "com.test");
- setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON);
+ installer.createSession(params);
writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
.putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2));
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
similarity index 88%
rename from robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
rename to tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
index 87b0887..005389e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
@@ -30,26 +30,31 @@
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
import android.os.Process;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LauncherModelHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
+import java.util.HashMap;
import java.util.HashSet;
/** Unit tests for {@link GridSizeMigrationTaskV2} */
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class GridSizeMigrationTaskV2Test {
private LauncherModelHelper mModelHelper;
@@ -73,7 +78,7 @@
@Before
public void setUp() {
mModelHelper = new LauncherModelHelper();
- mContext = RuntimeEnvironment.application;
+ mContext = mModelHelper.sandboxContext;
mDb = mModelHelper.provider.getDb();
mValidPackages = new HashSet<>();
@@ -98,8 +103,13 @@
LauncherSettings.Favorites.TMP_TABLE);
}
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
+ }
+
@Test
- public void testMigration() {
+ public void testMigration() throws Exception {
int[] srcHotseatItems = {
mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
@@ -134,17 +144,17 @@
// Check hotseat items
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
- "container=" + CONTAINER_HOTSEAT, null, null, null);
+ "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons);
int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
c.moveToNext();
- assertEquals(c.getInt(screenIndex), 1);
- assertTrue(c.getString(intentIndex).contains(testPackage2));
- c.moveToNext();
assertEquals(c.getInt(screenIndex), 0);
assertTrue(c.getString(intentIndex).contains(testPackage1));
c.moveToNext();
+ assertEquals(c.getInt(screenIndex), 1);
+ assertTrue(c.getString(intentIndex).contains(testPackage2));
+ c.moveToNext();
assertEquals(c.getInt(screenIndex), 2);
assertTrue(c.getString(intentIndex).contains(testPackage3));
c.moveToNext();
@@ -157,35 +167,24 @@
new String[]{LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
LauncherSettings.Favorites.INTENT},
"container=" + CONTAINER_DESKTOP, null, null, null);
- assertEquals(c.getCount(), 6);
intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
int cellXIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLX);
int cellYIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLY);
- c.moveToNext();
- assertTrue(c.getString(intentIndex).contains(testPackage7));
- c.moveToNext();
- assertTrue(c.getString(intentIndex).contains(testPackage6));
- assertEquals(c.getInt(cellXIndex), 0);
- assertEquals(c.getInt(cellYIndex), 3);
- c.moveToNext();
- assertTrue(c.getString(intentIndex).contains(testPackage10));
- assertEquals(c.getInt(cellXIndex), 1);
- assertEquals(c.getInt(cellYIndex), 3);
- c.moveToNext();
- assertTrue(c.getString(intentIndex).contains(testPackage5));
- assertEquals(c.getInt(cellXIndex), 2);
- assertEquals(c.getInt(cellYIndex), 3);
- c.moveToNext();
- assertTrue(c.getString(intentIndex).contains(testPackage9));
- assertEquals(c.getInt(cellXIndex), 3);
- assertEquals(c.getInt(cellYIndex), 3);
- c.moveToNext();
- assertTrue(c.getString(intentIndex).contains(testPackage8));
- assertEquals(c.getInt(cellXIndex), 0);
- assertEquals(c.getInt(cellYIndex), 2);
-
+ HashMap<String, Point> locMap = new HashMap<>();
+ while (c.moveToNext()) {
+ locMap.put(
+ Intent.parseUri(c.getString(intentIndex), 0).getPackage(),
+ new Point(c.getInt(cellXIndex), c.getInt(cellYIndex)));
+ }
c.close();
+
+ assertEquals(locMap.size(), 6);
+ assertEquals(new Point(0, 2), locMap.get(testPackage8));
+ assertEquals(new Point(0, 3), locMap.get(testPackage6));
+ assertEquals(new Point(1, 3), locMap.get(testPackage10));
+ assertEquals(new Point(2, 3), locMap.get(testPackage5));
+ assertEquals(new Point(3, 3), locMap.get(testPackage9));
}
@Test
@@ -212,7 +211,7 @@
// Check hotseat items
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
- "container=" + CONTAINER_HOTSEAT, null, null, null);
+ "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
assertEquals(c.getCount(), numSrcDatabaseHotseatIcons);
int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
@@ -257,7 +256,7 @@
// Check hotseat items
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
- "container=" + CONTAINER_HOTSEAT, null, null, null);
+ "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons);
int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
similarity index 93%
rename from robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
rename to tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 800311a..6444ef6 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model;
+import static androidx.test.InstrumentationRegistry.getContext;
+
import static com.android.launcher3.LauncherSettings.Favorites.CELLX;
import static com.android.launcher3.LauncherSettings.Favorites.CELLY;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
@@ -33,7 +35,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.SCREEN;
import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
import static com.android.launcher3.LauncherSettings.Favorites._ID;
-import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -41,37 +43,37 @@
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
-import static org.robolectric.Shadows.shadowOf;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.database.MatrixCursor;
import android.os.Process;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.PackageManagerHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
/**
* Tests for {@link LoaderCursor}
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class LoaderCursorTest {
+ private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
private MatrixCursor mCursor;
@@ -82,7 +84,8 @@
@Before
public void setup() {
- mContext = RuntimeEnvironment.application;
+ mModelHelper = new LauncherModelHelper();
+ mContext = mModelHelper.sandboxContext;
mIDP = InvariantDeviceProfile.INSTANCE.get(mContext);
mApp = LauncherAppState.getInstance(mContext);
@@ -97,6 +100,11 @@
ums.allUsers.put(0, Process.myUserHandle());
}
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
+ }
+
private void initCursor(int itemType, String title) {
mCursor.newRow()
.add(_ID, 1)
@@ -117,9 +125,7 @@
@Test
public void getAppShortcutInfo_dontAllowMissing_validComponent() throws Exception {
- ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_PACKAGE);
- shadowOf(mContext.getPackageManager()).addActivityIfNotPresent(cn);
-
+ ComponentName cn = new ComponentName(getContext(), TEST_ACTIVITY);
initCursor(ITEM_TYPE_APPLICATION, "");
assertTrue(mLoaderCursor.moveToNext());
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
similarity index 65%
rename from robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
rename to tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index 4319355..42c9f11 100644
--- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -15,69 +15,60 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.util.Executors.createAndStartNewLooper;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
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.mockito.Mockito.spy;
-import static org.robolectric.Shadows.shadowOf;
import android.os.Process;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.shadows.ShadowLooperExecutor;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.TestUtil;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
/**
* Tests to verify multiple callbacks in Loader
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class ModelMultiCallbacksTest {
private LauncherModelHelper mModelHelper;
- private ShadowPackageManager mSpm;
- private LooperExecutor mTempMainExecutor;
-
@Before
- public void setUp() throws Exception {
+ public void setUp() {
mModelHelper = new LauncherModelHelper();
- mModelHelper.installApp(TEST_PACKAGE);
+ }
- mSpm = shadowOf(RuntimeEnvironment.application.getPackageManager());
-
- // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
- // so that we can wait appropriately for the loader to complete.
- mTempMainExecutor = new LooperExecutor(createAndStartNewLooper("tempMain"));
- ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR);
- sle.setHandler(mTempMainExecutor.getHandler());
+ @After
+ public void tearDown() throws Exception {
+ mModelHelper.destroy();
+ TestUtil.uninstallDummyApp();
}
@Test
@@ -85,7 +76,7 @@
setupWorkspacePages(3);
MyCallbacks cb1 = spy(MyCallbacks.class);
- mModelHelper.getModel().addCallbacksAndLoad(cb1);
+ Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb1));
waitForLoaderAndTempMainThread();
cb1.verifySynchronouslyBound(3);
@@ -94,10 +85,10 @@
cb1.reset();
MyCallbacks cb2 = spy(MyCallbacks.class);
cb2.mPageToBindSync = IntSet.wrap(2);
- mModelHelper.getModel().addCallbacksAndLoad(cb2);
+ Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2));
waitForLoaderAndTempMainThread();
- cb1.verifySynchronouslyBound(3);
+ assertFalse(cb1.bindStarted);
cb2.verifySynchronouslyBound(3);
// Remove callbacks
@@ -105,7 +96,7 @@
cb2.reset();
// No effect on callbacks when removing an callback
- mModelHelper.getModel().removeCallbacks(cb2);
+ Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2));
waitForLoaderAndTempMainThread();
assertNull(cb1.mPendingTasks);
assertNull(cb2.mPendingTasks);
@@ -119,52 +110,48 @@
@Test
public void testTwoCallbacks_receiveUpdates() throws Exception {
+ TestUtil.uninstallDummyApp();
+
setupWorkspacePages(1);
MyCallbacks cb1 = spy(MyCallbacks.class);
MyCallbacks cb2 = spy(MyCallbacks.class);
- mModelHelper.getModel().addCallbacksAndLoad(cb1);
- mModelHelper.getModel().addCallbacksAndLoad(cb2);
+ Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb1));
+ Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2));
waitForLoaderAndTempMainThread();
- cb1.verifyApps(TEST_PACKAGE);
- cb2.verifyApps(TEST_PACKAGE);
+ assertTrue(cb1.allApps().contains(TEST_PACKAGE));
+ assertTrue(cb2.allApps().contains(TEST_PACKAGE));
// Install package 1
- String pkg1 = "com.test.pkg1";
- mModelHelper.installApp(pkg1);
- mModelHelper.getModel().onPackageAdded(pkg1, Process.myUserHandle());
+ TestUtil.installDummyApp();
+ mModelHelper.getModel().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle());
waitForLoaderAndTempMainThread();
- cb1.verifyApps(TEST_PACKAGE, pkg1);
- cb2.verifyApps(TEST_PACKAGE, pkg1);
-
- // Install package 2
- String pkg2 = "com.test.pkg2";
- mModelHelper.installApp(pkg2);
- mModelHelper.getModel().onPackageAdded(pkg2, Process.myUserHandle());
- waitForLoaderAndTempMainThread();
- cb1.verifyApps(TEST_PACKAGE, pkg1, pkg2);
- cb2.verifyApps(TEST_PACKAGE, pkg1, pkg2);
+ assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE));
+ assertTrue(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE));
// Uninstall package 2
- mSpm.removePackage(pkg1);
- mModelHelper.getModel().onPackageRemoved(pkg1, Process.myUserHandle());
+ TestUtil.uninstallDummyApp();
+ mModelHelper.getModel().onPackageRemoved(TestUtil.DUMMY_PACKAGE, Process.myUserHandle());
waitForLoaderAndTempMainThread();
- cb1.verifyApps(TEST_PACKAGE, pkg2);
- cb2.verifyApps(TEST_PACKAGE, pkg2);
+ assertFalse(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE));
+ assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE));
// Unregister a callback and verify updates no longer received
- mModelHelper.getModel().removeCallbacks(cb2);
- mSpm.removePackage(pkg2);
- mModelHelper.getModel().onPackageRemoved(pkg2, Process.myUserHandle());
+ Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2));
+ TestUtil.installDummyApp();
+ mModelHelper.getModel().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle());
waitForLoaderAndTempMainThread();
- cb1.verifyApps(TEST_PACKAGE);
- cb2.verifyApps(TEST_PACKAGE, pkg2);
+
+ // cb2 didn't get the update
+ assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE));
+ assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE));
}
private void waitForLoaderAndTempMainThread() throws Exception {
+ Executors.MAIN_EXECUTOR.submit(() -> { }).get();
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
- mTempMainExecutor.submit(() -> { }).get();
+ Executors.MAIN_EXECUTOR.submit(() -> { }).get();
}
private void setupWorkspacePages(int pageCount) throws Exception {
@@ -183,10 +170,16 @@
IntSet mPageBoundSync = new IntSet();
RunnableList mPendingTasks;
AppInfo[] mAppInfos;
+ boolean bindStarted;
MyCallbacks() { }
@Override
+ public void startBinding() {
+ bindStarted = true;
+ }
+
+ @Override
public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
mPageBoundSync = boundPages;
mPendingTasks = pendingTasks;
@@ -212,10 +205,12 @@
mPageBoundSync = new IntSet();
mPendingTasks = null;
mAppInfos = null;
+ bindStarted = false;
}
public void verifySynchronouslyBound(int totalItems) {
// Verify that the requested page is bound synchronously
+ assertTrue(bindStarted);
assertEquals(mPageToBindSync, mPageBoundSync);
assertEquals(mItems.size(), 1);
assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync);
@@ -226,12 +221,14 @@
assertEquals(mItems.size(), totalItems);
}
- public void verifyApps(String... apps) {
- assertEquals(apps.length, mAppInfos.length);
- assertEquals(Arrays.stream(mAppInfos)
+ public Set<String> allApps() {
+ return Arrays.stream(mAppInfos)
.map(ai -> ai.getTargetComponent().getPackageName())
- .collect(Collectors.toSet()),
- new HashSet<>(Arrays.asList(apps)));
+ .collect(Collectors.toSet());
+ }
+
+ public void verifyApps(String... apps) {
+ assertTrue(allApps().containsAll(Arrays.asList(apps)));
}
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
similarity index 87%
rename from robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
rename to tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index 412ace0..519191e 100644
--- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -2,18 +2,19 @@
import static org.junit.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
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.pm.PackageInstallInfo;
import com.android.launcher3.util.LauncherModelHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
import java.util.Arrays;
import java.util.HashSet;
@@ -21,8 +22,8 @@
/**
* Tests for {@link PackageInstallStateChangedTask}
*/
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class PackageInstallStateChangedTaskTest {
private LauncherModelHelper mModelHelper;
@@ -30,7 +31,12 @@
@Before
public void setup() throws Exception {
mModelHelper = new LauncherModelHelper();
- mModelHelper.initializeData("/package_install_state_change_task_data.txt");
+ mModelHelper.initializeData("package_install_state_change_task_data");
+ }
+
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
}
private PackageInstallStateChangedTask newTask(String pkg, int progress) {
@@ -66,7 +72,7 @@
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
if (info instanceof WorkspaceItemInfo) {
- assertEquals(updates.contains(info.id) ? progress: 0,
+ assertEquals(updates.contains(info.id) ? progress: 100,
((WorkspaceItemInfo) info).getProgressLevel());
} else {
assertEquals(updates.contains(info.id) ? progress: -1,
diff --git a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
similarity index 95%
rename from robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
rename to tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
index 83bf7da..6764e09 100644
--- a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
+++ b/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
@@ -16,6 +16,8 @@
package com.android.launcher3.popup;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.popup.PopupPopulator.NUM_DYNAMIC;
@@ -27,10 +29,11 @@
import android.content.pm.ShortcutInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Collections;
@@ -39,7 +42,8 @@
/**
* Tests the sorting and filtering of shortcuts in {@link PopupPopulator}.
*/
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class PopupPopulatorTest {
@Test
@@ -137,7 +141,7 @@
private ShortcutInfo createInfo(boolean isStatic, int rank) {
ShortcutInfo info = spy(new ShortcutInfo.Builder(
- RuntimeEnvironment.application, generateId(isStatic, rank))
+ getApplicationContext(), generateId(isStatic, rank))
.setRank(rank)
.build());
doReturn(isStatic).when(info).isDeclaredInManifest();
diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
similarity index 92%
rename from robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
rename to tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 4184d33..48305ee 100644
--- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -21,18 +21,21 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
/**
* Tests for {@link RestoreDbTask}
*/
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class RestoreDbTaskTest {
@Test
@@ -95,7 +98,7 @@
private final long mProfileId;
MyDatabaseHelper(long profileId) {
- super(RuntimeEnvironment.application, null, false);
+ super(InstrumentationRegistry.getInstrumentation().getTargetContext(), null, false);
mProfileId = profileId;
}
diff --git a/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java b/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java
new file mode 100644
index 0000000..fd86cf1
--- /dev/null
+++ b/tests/src/com/android/launcher3/secondarydisplay/SDLauncherTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.secondarydisplay;
+
+import static androidx.test.core.app.ActivityScenario.launch;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.espresso.intent.Intents;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link SecondaryDisplayLauncher}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SDLauncherTest {
+
+ @Before
+ public void setUp() {
+ Intents.init();
+ }
+
+ @After
+ public void tearDown() {
+ Intents.release();
+ }
+
+ @Test
+ public void testAllAppsListOpens() {
+ ActivityScenario<SecondaryDisplayLauncher> launcher =
+ launch(SecondaryDisplayLauncher.class);
+ launcher.onActivity(l -> l.showAppDrawer(true));
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java b/tests/src/com/android/launcher3/settings/SettingsActivityTest.java
similarity index 96%
rename from robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java
rename to tests/src/com/android/launcher3/settings/SettingsActivityTest.java
index 3271812..1c205f0 100644
--- a/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java
+++ b/tests/src/com/android/launcher3/settings/SettingsActivityTest.java
@@ -55,6 +55,7 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,6 +76,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_aboutTap_launchesActivity() {
ActivityScenario.launch(SettingsActivity.class);
onView(withId(R.id.recycler_view)).perform(
@@ -88,6 +90,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_developerOptionsTap_launchesActivityWithFragment() {
PluginPrefs.setHasPlugins(mApplicationContext);
ActivityScenario.launch(SettingsActivity.class);
@@ -100,6 +103,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_aboutScreenIntent() {
Bundle fragmentArgs = new Bundle();
fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen");
@@ -114,6 +118,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_developerOptionsFragmentIntent() {
Intent intent = new Intent(mApplicationContext, SettingsActivity.class)
.putExtra(EXTRA_FRAGMENT, DeveloperOptionsFragment.class.getName());
@@ -125,6 +130,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_intentWithUnknownFragment() {
String fragmentClass = PreferenceFragmentCompat.class.getName();
Intent intent = new Intent(mApplicationContext, SettingsActivity.class)
@@ -139,6 +145,7 @@
}
@Test
+ @Ignore // b/199309785
public void testSettings_backButtonFinishesActivity() {
Bundle fragmentArgs = new Bundle();
fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen");
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 0d5b9ad..1a6ce8c 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -52,7 +52,6 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
-import com.android.launcher3.common.WidgetUtils;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -63,6 +62,7 @@
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageManagerHelper;
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.ScreenRecordRule;
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4beb617..881f50c 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -36,7 +36,6 @@
import com.android.launcher3.tapl.AppIconMenuItem;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.WidgetsRecyclerView;
@@ -97,7 +96,6 @@
}
@Test
- @ScreenRecord //b/187080582
public void testDevicePressMenu() throws Exception {
mDevice.pressMenu();
mDevice.waitForIdle();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 2e8048a..dad4f2b 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -27,7 +27,6 @@
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -81,7 +80,6 @@
*/
@Test
@PortraitLandscape
- @ScreenRecord //b/195263971
public void testDragCustomShortcut() throws Throwable {
clearHomescreen();
mDevice.pressHome();
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 9c6c317..fa39ce0 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -17,7 +17,7 @@
import static androidx.test.InstrumentationRegistry.getTargetContext;
-import static com.android.launcher3.common.WidgetUtils.createWidgetInfo;
+import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index a5694fc..ccbb662 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -42,7 +42,6 @@
import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.Wait.Condition;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Before;
@@ -78,7 +77,6 @@
public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ }
@Test
- @ScreenRecord //b/192010616
public void testPinWidgetNoConfig() throws Throwable {
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
diff --git a/tests/src/com/android/launcher3/util/ActivityContextWrapper.java b/tests/src/com/android/launcher3/util/ActivityContextWrapper.java
new file mode 100644
index 0000000..2618a2e
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ActivityContextWrapper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.view.ContextThemeWrapper;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * {@link ContextWrapper} with internal Launcher interface for testing
+ */
+public class ActivityContextWrapper extends ContextThemeWrapper implements ActivityContext {
+
+ private final DeviceProfile mProfile;
+ private final MyDragLayer mMyDragLayer;
+
+ public ActivityContextWrapper(Context base) {
+ super(base, android.R.style.Theme_DeviceDefault);
+ mProfile = InvariantDeviceProfile.INSTANCE.get(base).getDeviceProfile(base).copy(base);
+ mMyDragLayer = new MyDragLayer(this);
+ }
+
+ @Override
+ public BaseDragLayer getDragLayer() {
+ return mMyDragLayer;
+ }
+
+ @Override
+ public DeviceProfile getDeviceProfile() {
+ return mProfile;
+ }
+
+ private static class MyDragLayer extends BaseDragLayer<ActivityContextWrapper> {
+
+ MyDragLayer(Context context) {
+ super(context, null, 1);
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = new TouchController[0];
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java b/tests/src/com/android/launcher3/util/GridOccupancyTest.java
similarity index 92%
rename from robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
rename to tests/src/com/android/launcher3/util/GridOccupancyTest.java
index 2f3fc37..b498077 100644
--- a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java
+++ b/tests/src/com/android/launcher3/util/GridOccupancyTest.java
@@ -4,14 +4,17 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link GridOccupancy}
*/
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class GridOccupancyTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java b/tests/src/com/android/launcher3/util/IntArrayTest.java
similarity index 86%
rename from robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
rename to tests/src/com/android/launcher3/util/IntArrayTest.java
index c08e198..a3c7007 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
+++ b/tests/src/com/android/launcher3/util/IntArrayTest.java
@@ -17,14 +17,17 @@
import static com.google.common.truth.Truth.assertThat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
/**
- * Robolectric unit tests for {@link IntArray}
+ * Unit tests for {@link IntArray}
*/
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class IntArrayTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/tests/src/com/android/launcher3/util/IntSetTest.java
similarity index 91%
rename from robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
rename to tests/src/com/android/launcher3/util/IntSetTest.java
index 7a8c00b..cdb2891 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
+++ b/tests/src/com/android/launcher3/util/IntSetTest.java
@@ -21,14 +21,17 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
/**
- * Robolectric unit tests for {@link IntSet}
+ * Unit tests for {@link IntSet}
*/
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class IntSetTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
similarity index 100%
rename from robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
rename to tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
similarity index 61%
rename from robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
rename to tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 846e201..59966ee 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -15,26 +15,40 @@
*/
package com.android.launcher3.util;
-import static android.content.Intent.ACTION_CREATE_SHORTCUT;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+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 static org.robolectric.Shadows.shadowOf;
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.IntentFilter;
-import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
import android.database.sqlite.SQLiteDatabase;
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;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -45,27 +59,29 @@
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.shadows.ShadowLooperExecutor;
+import com.android.launcher3.testing.TestInformationProvider;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import org.mockito.ArgumentCaptor;
-import org.robolectric.Robolectric;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowContentResolver;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.io.InputStreamReader;
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;
@@ -81,7 +97,9 @@
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 = "com.android.launcher3.validpackage";
+
+ public static final String TEST_PACKAGE = testContext().getPackageName();
+ public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2";
// Authority for providing a test default-workspace-layout data.
private static final String TEST_PROVIDER_AUTHORITY =
@@ -90,21 +108,42 @@
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;
- private final long mDefaultProfileId;
+ public final SanboxModelContext sandboxContext;
+
+ public final long defaultProfileId;
private BgDataModel mDataModel;
private AllAppsList mAllAppsList;
public LauncherModelHelper() {
- provider = Robolectric.setupContentProvider(TestLauncherProvider.class);
- mDefaultProfileId = UserCache.INSTANCE.get(RuntimeEnvironment.application)
+ 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 SanboxModelContext();
+ defaultProfileId = UserCache.INSTANCE.get(sandboxContext)
.getSerialNumberForUser(Process.myUserHandle());
- ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider);
+ setupProvider(LauncherProvider.AUTHORITY, provider);
+ }
+
+ protected 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());
}
public LauncherModel getModel() {
- return LauncherAppState.getInstance(RuntimeEnvironment.application).getModel();
+ return LauncherAppState.getInstance(sandboxContext).getModel();
}
public synchronized BgDataModel getBgDataModel() {
@@ -121,6 +160,28 @@
return mAllAppsList;
}
+ public void destroy() {
+ // When destroying the context, make sure that the model thread is blocked, so that no
+ // new jobs get posted while we are cleaning up
+ CountDownLatch l1 = new CountDownLatch(1);
+ CountDownLatch l2 = new CountDownLatch(1);
+ MODEL_EXECUTOR.execute(() -> {
+ l1.countDown();
+ waitOrThrow(l2);
+ });
+ waitOrThrow(l1);
+ sandboxContext.onDestroy();
+ l2.countDown();
+ }
+
+ private void waitOrThrow(CountDownLatch latch) {
+ try {
+ latch.await();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Synchronously executes the task and returns all the UI callbacks posted.
*/
@@ -161,13 +222,16 @@
* Initializes mock data for the test.
*/
public void initializeData(String resourceName) throws Exception {
- Context targetContext = RuntimeEnvironment.application;
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(
- this.getClass().getResourceAsStream(resourceName)))) {
+ resources.openRawResource(resId)))) {
String line;
HashMap<String, Class> classMap = new HashMap<>();
while ((line = reader.readLine()) != null) {
@@ -181,7 +245,7 @@
classMap.put(commands[1], Class.forName(commands[2]));
break;
case "bgItem":
- bgDataModel.addItem(targetContext,
+ bgDataModel.addItem(sandboxContext,
(ItemInfo) initItem(classMap.get(commands[1]), commands, 2),
false);
break;
@@ -236,7 +300,7 @@
}
public int addItem(int type, int screen, int container, int x, int y) {
- return addItem(type, screen, container, x, y, mDefaultProfileId, TEST_PACKAGE);
+ 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) {
@@ -244,12 +308,12 @@
}
public int addItem(int type, int screen, int container, int x, int y, String packageName) {
- return addItem(type, screen, container, x, y, mDefaultProfileId, 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, mDefaultProfileId, packageName, id, contentUri);
+ addItem(type, screen, container, x, y, defaultProfileId, packageName, id, contentUri);
return id;
}
@@ -260,8 +324,7 @@
*/
public int addItem(int type, int screen, int container, int x, int y, long profileId,
String packageName) {
- Context context = RuntimeEnvironment.application;
- int id = LauncherSettings.Settings.call(context.getContentResolver(),
+ 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);
@@ -270,8 +333,6 @@
public void addItem(int type, int screen, int container, int x, int y, long profileId,
String packageName, int id, Uri contentUri) {
- Context context = RuntimeEnvironment.application;
-
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites._ID, id);
values.put(LauncherSettings.Favorites.CONTAINER, container);
@@ -295,7 +356,7 @@
}
}
- context.getContentResolver().insert(contentUri, values);
+ sandboxContext.getContentResolver().insert(contentUri, values);
}
public int[][][] createGrid(int[][][] typeArray) {
@@ -303,12 +364,11 @@
}
public int[][][] createGrid(int[][][] typeArray, int startScreen) {
- final Context context = RuntimeEnvironment.application;
- LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
- return createGrid(typeArray, startScreen, mDefaultProfileId);
+ return createGrid(typeArray, startScreen, defaultProfileId);
}
/**
@@ -320,14 +380,13 @@
* @return the same grid representation where each entry is the corresponding item id.
*/
public int[][][] createGrid(int[][][] typeArray, int startScreen, long profileId) {
- Context context = RuntimeEnvironment.application;
int[][][] ids = new int[typeArray.length][][];
for (int i = 0; i < typeArray.length; i++) {
// Add screen to DB
int screenId = startScreen + i;
// Keep the screen id counter up to date
- LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
ids[i] = new int[typeArray[i].length][];
@@ -353,69 +412,45 @@
*/
public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
throws Exception {
- Context context = RuntimeEnvironment.application;
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(sandboxContext);
idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE;
idp.iconBitmapSize = DEFAULT_BITMAP_SIZE;
- Settings.Secure.putString(context.getContentResolver(),
- "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+ "settings put secure launcher3.layout.provider " + TEST_PROVIDER_AUTHORITY);
+ ContentProvider cp = new TestInformationProvider() {
- shadowOf(context.getPackageManager())
- .addProviderIfNotPresent(new ComponentName("com.test", "Mock")).authority =
- TEST_PROVIDER_AUTHORITY;
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- builder.build(new OutputStreamWriter(bos));
- Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, context);
- shadowOf(context.getContentResolver()).registerInputStream(layoutUri,
- new ByteArrayInputStream(bos.toByteArray()));
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ try {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ AutoCloseOutputStream outputStream = new AutoCloseOutputStream(pipe[1]);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ builder.build(new OutputStreamWriter(bos));
+ outputStream.write(bos.toByteArray());
+ outputStream.flush();
+ outputStream.close();
+ return pipe[0];
+ } catch (Exception e) {
+ throw new FileNotFoundException(e.getMessage());
+ }
+ }
+ };
+ setupProvider(TEST_PROVIDER_AUTHORITY, cp);
return this;
}
/**
- * Simulates an apk install with a default main activity with same class and package name
- */
- public void installApp(String component) throws NameNotFoundException {
- IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
- filter.addCategory(Intent.CATEGORY_LAUNCHER);
- installApp(component, component, filter);
- }
-
- /**
- * Simulates a custom shortcut install
- */
- public void installCustomShortcut(String pkg, String clazz) throws NameNotFoundException {
- installApp(pkg, clazz, new IntentFilter(ACTION_CREATE_SHORTCUT));
- }
-
- private void installApp(String pkg, String clazz, IntentFilter filter)
- throws NameNotFoundException {
- ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager());
- ComponentName cn = new ComponentName(pkg, clazz);
- spm.addActivityIfNotPresent(cn);
-
- filter.addCategory(Intent.CATEGORY_DEFAULT);
- spm.addIntentFilterForActivity(cn, filter);
- }
-
- /**
* Loads the model in memory synchronously
*/
public void loadModelSync() throws ExecutionException, InterruptedException {
- // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
- // so that we can wait appropriately for the loader to complete.
- ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR);
- sle.setHandler(Executors.UI_HELPER_EXECUTOR.getHandler());
-
- Callbacks mockCb = mock(Callbacks.class);
- getModel().addCallbacksAndLoad(mockCb);
+ Callbacks mockCb = new Callbacks() { };
+ Executors.MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
- Executors.UI_HELPER_EXECUTOR.submit(() -> { }).get();
-
- sle.setHandler(null);
- getModel().removeCallbacks(mockCb);
+ Executors.MAIN_EXECUTOR.submit(() -> { }).get();
+ Executors.MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
}
/**
@@ -437,4 +472,91 @@
return mOpenHelper;
}
}
+
+ 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 SanboxModelContext extends SandboxContext {
+
+ private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
+ private final PackageManager mPm;
+ private final File mDbDir;
+
+ SanboxModelContext() {
+ super(ApplicationProvider.getApplicationContext(),
+ UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
+ LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
+ DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
+ SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
+ ItemInstallQueue.INSTANCE);
+ mPm = spy(getBaseContext().getPackageManager());
+ mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
+ }
+
+ public SanboxModelContext allow(MainThreadInitializedObject object) {
+ mAllowedObjects.add(object);
+ return this;
+ }
+
+ @Override
+ public File getDatabasePath(String name) {
+ if (!mDbDir.exists()) {
+ mDbDir.mkdirs();
+ }
+ return new File(mDbDir, name);
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mMockResolver;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (deleteContents(mDbDir)) {
+ mDbDir.delete();
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPm;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ Object service = mSpiedServices.get(name);
+ return service != null ? service : super.getSystemService(name);
+ }
+
+ public <T> T spyService(Class<T> tClass) {
+ String name = getSystemServiceName(tClass);
+ Object service = mSpiedServices.get(name);
+ if (service != null) {
+ return (T) service;
+ }
+
+ T result = spy(getSystemService(tClass));
+ mSpiedServices.put(name, result);
+ return result;
+ }
+ }
+
+ private static Context testContext() {
+ return getInstrumentation().getContext();
+ }
}
diff --git a/tests/src/com/android/launcher3/util/ReflectionHelpers.java b/tests/src/com/android/launcher3/util/ReflectionHelpers.java
new file mode 100644
index 0000000..d89975d
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ReflectionHelpers.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import java.lang.reflect.Field;
+
+public class ReflectionHelpers {
+
+ /**
+ * Reflectively get the value of a field.
+ *
+ * @param object Target object.
+ * @param fieldName The field name.
+ * @param <R> The return type.
+ * @return Value of the field on the object.
+ */
+ public static <R> R getField(Object object, String fieldName) {
+ try {
+ Field field = object.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return (R) field.get(object);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Reflectively set the value of a field.
+ *
+ * @param object Target object.
+ * @param fieldName The field name.
+ * @param fieldNewValue New value.
+ */
+ public static void setField(Object object, String fieldName, Object fieldNewValue) {
+ try {
+ Field field = object.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(object, fieldNewValue);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index fe6143c..8b5e197 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -52,7 +52,7 @@
throw new RuntimeException(t);
}
Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis());
- launcher.checkForAnomaly();
+ launcher.checkForAnomaly(false);
Assert.fail(message.get());
}
diff --git a/tests/src_common/com/android/launcher3/common/WidgetUtils.java b/tests/src/com/android/launcher3/util/WidgetUtils.java
similarity index 82%
rename from tests/src_common/com/android/launcher3/common/WidgetUtils.java
rename to tests/src/com/android/launcher3/util/WidgetUtils.java
index 97500e3..6fc8491 100644
--- a/tests/src_common/com/android/launcher3/common/WidgetUtils.java
+++ b/tests/src/com/android/launcher3/util/WidgetUtils.java
@@ -13,19 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.common;
+package com.android.launcher3.util;
+
+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.AppWidgetHost;
+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.util.ContentWriter;
import com.android.launcher3.widget.LauncherAppWidgetHost;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -102,4 +108,17 @@
resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
writer.getValues(targetContext));
}
+
+
+ /**
+ * Creates a {@link AppWidgetProviderInfo} for the provided component name
+ */
+ public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+ AppWidgetProviderInfo info = AppWidgetManager.getInstance(getApplicationContext())
+ .getInstalledProvidersForPackage(
+ getInstrumentation().getContext().getPackageName(), Process.myUserHandle())
+ .get(0);
+ info.provider = cn;
+ return info;
+ }
}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 60529a4..f9a9997 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -15,6 +15,7 @@
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
+import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -30,6 +31,7 @@
public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) {
mDevice = device;
mLauncher = launcher;
+ Log.d("b/196820244", "FailureWatcher.ctor", new Exception());
}
@Override
@@ -44,7 +46,9 @@
@Override
public void evaluate() throws Throwable {
try {
- base.evaluate();
+ Log.d("b/196820244", "Before evaluate");
+ FailureWatcher.super.apply(base, description).evaluate();
+ Log.d("b/196820244", "After evaluate");
} finally {
if (mLauncher.hadNontestEvents()) {
throw new AssertionError(
@@ -63,13 +67,18 @@
onError(mDevice, description, e);
}
+ static File diagFile(Description description, String prefix, String ext) {
+ return new File(getInstrumentation().getTargetContext().getFilesDir(),
+ prefix + "-" + description.getTestClass().getSimpleName() + "."
+ + description.getMethodName() + "." + ext);
+ }
+
public static void onError(UiDevice device, Description description, Throwable e) {
+ Log.d("b/196820244", "onError 1");
if (device == null) return;
- final File parentFile = getInstrumentation().getTargetContext().getFilesDir();
- final File sceenshot = new File(parentFile,
- "TestScreenshot-" + description.getMethodName() + ".png");
- final File hierarchy = new File(parentFile,
- "Hierarchy-" + description.getMethodName() + ".zip");
+ Log.d("b/196820244", "onError 2");
+ final File sceenshot = diagFile(description, "TestScreenshot", "png");
+ final File hierarchy = diagFile(description, "Hierarchy", "zip");
// Dump window hierarchy
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(hierarchy))) {
@@ -92,13 +101,13 @@
device.takeScreenshot(sceenshot);
// Dump accessibility hierarchy
- final File accessibilityHierarchyFile = new File(parentFile,
- "AccessibilityHierarchy-" + description.getMethodName() + ".uix");
try {
- device.dumpWindowHierarchy(accessibilityHierarchyFile);
+ device.dumpWindowHierarchy(diagFile(description, "AccessibilityHierarchy", "uix"));
} catch (IOException ex) {
Log.e(TAG, "Failed to save accessibility hierarchy", ex);
}
+
+ dumpCommand("logcat -d -s TestRunner", diagFile(description, "FilteredLogcat", "txt"));
}
private static void dumpStringCommand(String cmd, OutputStream out) throws IOException {
@@ -106,6 +115,14 @@
dumpCommand(cmd, out);
}
+ private static void dumpCommand(String cmd, File out) {
+ try (BufferedOutputStream buffered = new BufferedOutputStream(
+ new FileOutputStream(out))) {
+ dumpCommand(cmd, buffered);
+ } catch (IOException ex) {
+ }
+ }
+
private static void dumpCommand(String cmd, OutputStream out) throws IOException {
try (AutoCloseInputStream in = new AutoCloseInputStream(getInstrumentation()
.getUiAutomation().executeShellCommand(cmd))) {
diff --git a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
index 2b2fef4..08953fc 100644
--- a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
@@ -21,7 +21,6 @@
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
-import android.util.Log;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
@@ -103,8 +102,6 @@
*/
public static ShellCommandRule setDefaultLauncher() {
final ActivityInfo launcher = getLauncherInMyProcess();
- Log.d("b/187080582", "Launcher: " + new ComponentName(launcher.packageName, launcher.name)
- .flattenToString());
return new ShellCommandRule(getLauncherCommand(launcher), null, true, () ->
Assert.assertEquals("Setting default launcher failed",
new ComponentName(launcher.packageName, launcher.name)
diff --git a/robolectric_tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java b/tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
similarity index 96%
rename from robolectric_tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
rename to tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
index a6f892c..24ae583 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
+++ b/tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.widget;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -25,6 +27,9 @@
import android.graphics.Point;
import android.graphics.Rect;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@@ -32,15 +37,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class LauncherAppWidgetProviderInfoTest {
private static final int CELL_SIZE = 50;
@@ -51,8 +54,7 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = getApplicationContext();
}
@Test
@@ -260,6 +262,7 @@
return null;
}).when(profile).getCellSize(any(Point.class));
Mockito.when(profile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE));
+ Mockito.when(profile.shouldInsetWidgets()).thenReturn(true);
InvariantDeviceProfile idp = new InvariantDeviceProfile();
List<DeviceProfile> supportedProfiles = new ArrayList<>(idp.supportedProfiles);
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
similarity index 95%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
rename to tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
index b9f183c..6232938 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsDiffReporterTest.java
@@ -15,13 +15,16 @@
*/
package com.android.launcher3.widget.picker;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -30,6 +33,8 @@
import android.os.UserHandle;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.BitmapInfo;
@@ -48,15 +53,12 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsDiffReporterTest {
private static final String TEST_PACKAGE_PREFIX = "com.android.test";
private static final WidgetListBaseRowEntryComparator COMPARATOR =
@@ -87,7 +89,7 @@
.getComponent().getPackageName())
.when(mIconCache).getTitleNoCache(any());
- mContext = RuntimeEnvironment.application;
+ mContext = getApplicationContext();
mWidgetsDiffReporter = new WidgetsDiffReporter(mIconCache, mAdapter);
mHeaderA = createWidgetsHeaderEntry(TEST_PACKAGE_PREFIX + "A",
/* appName= */ "A", /* numOfWidgets= */ 3);
@@ -294,14 +296,10 @@
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = cn;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(cn));
+ AppWidgetProviderInfo widgetInfo = createAppWidgetProviderInfo(cn);
WidgetItem widgetItem = new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
similarity index 91%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
rename to tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index c730fc0..44d6964 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -15,12 +15,13 @@
*/
package com.android.launcher3.widget.picker;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
-import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -31,6 +32,8 @@
import android.view.LayoutInflater;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.BitmapInfo;
@@ -38,8 +41,9 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
+import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -51,20 +55,20 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+/**
+ * Unit tests for WidgetsListAdapter
+ * Note that all indices matching are shifted by 1 to account for the empty space at the start.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsListAdapterTest {
private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";
@Mock private LayoutInflater mMockLayoutInflater;
- @Mock private DatabaseWidgetPreviewLoader mMockWidgetCache;
@Mock private RecyclerView.AdapterDataObserver mListener;
@Mock private IconCache mIconCache;
@@ -76,13 +80,13 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
mUserHandle = Process.myUserHandle();
- mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
- mIconCache, null, null);
+ mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater,
+ mIconCache, () -> 0, null, null);
mAdapter.registerAdapterDataObserver(mListener);
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
@@ -102,7 +106,7 @@
mAdapter.setWidgets(generateSampleMap(1));
mAdapter.setWidgets(generateSampleMap(2));
- verify(mListener).onItemRangeInserted(eq(1), eq(1));
+ verify(mListener).onItemRangeInserted(eq(2), eq(1));
}
@Test
@@ -110,7 +114,7 @@
mAdapter.setWidgets(generateSampleMap(2));
mAdapter.setWidgets(generateSampleMap(1));
- verify(mListener).onItemRangeRemoved(eq(1), eq(1));
+ verify(mListener).onItemRangeRemoved(eq(2), eq(1));
}
@Test
@@ -118,7 +122,7 @@
mAdapter.setWidgets(generateSampleMap(1));
mAdapter.setWidgets(generateSampleMap(1));
- verify(mListener).onItemRangeChanged(eq(0), eq(1), isNull());
+ verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
}
@Test
@@ -137,7 +141,7 @@
// THEN the visible entries list becomes:
// [com.google.test0, com.google.test1, com.google.test1 content, com.google.test2]
// com.google.test.1 content is inserted into position 2.
- verify(mListener).onItemRangeInserted(eq(2), eq(1));
+ verify(mListener).onItemRangeInserted(eq(3), eq(1));
}
@Test
@@ -165,7 +169,7 @@
mAdapter.setWidgets(allEntries);
// THEN the onItemRangeChanged is invoked for "com.google.test1 content" at index 2.
- verify(mListener).onItemRangeChanged(eq(2), eq(1), isNull());
+ verify(mListener).onItemRangeChanged(eq(3), eq(1), isNull());
}
@Test
@@ -196,15 +200,16 @@
allAppsWithWidgets.get(6), allAppsWithWidgets.get(7));
mAdapter.setWidgets(newList);
+ // Account for 1st items as empty space
// Computation logic | [Intermediate list during computation]
// THEN B <> C < 0, removed B from index 1 | [A, E]
- verify(mListener).onItemRangeRemoved(/* positionStart= */ 1, /* itemCount= */ 1);
+ verify(mListener).onItemRangeRemoved(/* positionStart= */ 2, /* itemCount= */ 1);
// THEN E <> C > 0, C inserted to index 1 | [A, C, E]
- verify(mListener).onItemRangeInserted(/* positionStart= */ 1, /* itemCount= */ 1);
- // THEN E <> D > 0, D inserted to index 2 | [A, C, D, E]
verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1);
+ // THEN E <> D > 0, D inserted to index 2 | [A, C, D, E]
+ verify(mListener).onItemRangeInserted(/* positionStart= */ 3, /* itemCount= */ 1);
// THEN E <> null = -1, E deleted from index 3 | [A, C, D]
- verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
+ verify(mListener).onItemRangeRemoved(/* positionStart= */ 4, /* itemCount= */ 1);
}
@Test
@@ -227,8 +232,8 @@
// THEN expanded app is reset and the visible entries list becomes:
// [com.google.test0, com.google.test1, com.google.test2]
- verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
- verify(mListener).onItemRangeRemoved(/* positionStart= */ 2, /* itemCount= */ 1);
+ verify(mListener).onItemRangeChanged(eq(2), eq(1), isNull());
+ verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
}
/**
@@ -265,14 +270,10 @@
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = cn;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(cn));
+ AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
similarity index 71%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
rename to tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 81b0c5f..969c12a 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -15,13 +15,16 @@
*/
package com.android.launcher3.widget.picker;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
-import static org.robolectric.Shadows.shadowOf;
+
+import static java.util.Collections.EMPTY_LIST;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -31,7 +34,9 @@
import android.widget.FrameLayout;
import android.widget.TextView;
-import com.android.launcher3.DeviceProfile;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
@@ -39,29 +44,23 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.testing.TestActivity;
+import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
+import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.android.controller.ActivityController;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsListHeaderViewHolderBinderTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
@@ -69,65 +68,41 @@
private Context mContext;
private WidgetsListHeaderViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
- // Replace ActivityController with ActivityScenario, which is the recommended way for activity
- // testing.
- private ActivityController<TestActivity> mActivityController;
- private TestActivity mTestActivity;
@Mock
private IconCache mIconCache;
@Mock
- private DeviceProfile mDeviceProfile;
- @Mock
- private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- @Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+
+ mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
- mActivityController = Robolectric.buildActivity(TestActivity.class);
- mTestActivity = mActivityController.setup().get();
- mTestActivity.setDeviceProfile(mDeviceProfile);
-
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
-
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
- LayoutInflater.from(mTestActivity),
+ LayoutInflater.from(mContext),
mOnHeaderClickListener,
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
- }
-
- @After
- public void tearDown() {
- mActivityController.destroy();
+ new WidgetsListDrawableFactory(mContext));
}
@Test
public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
- new FrameLayout(mTestActivity));
+ new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListHeaderEntry entry = generateSampleAppHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -138,14 +113,14 @@
@Test
public void bindViewHolder_shouldAttachOnHeaderClickListener() {
WidgetsListHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
- new FrameLayout(mTestActivity));
+ new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListHeaderEntry entry = generateSampleAppHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
widgetsListHeader.callOnClick();
verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
@@ -164,14 +139,10 @@
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = cn;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(cn));
+ AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
similarity index 71%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
rename to tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index a0ba7c3..453f4fb 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -15,13 +15,16 @@
*/
package com.android.launcher3.widget.picker;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
-import static org.robolectric.Shadows.shadowOf;
+
+import static java.util.Collections.EMPTY_LIST;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -31,7 +34,9 @@
import android.widget.FrameLayout;
import android.widget.TextView;
-import com.android.launcher3.DeviceProfile;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
@@ -39,29 +44,23 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.testing.TestActivity;
+import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
+import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.android.controller.ActivityController;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsListSearchHeaderViewHolderBinderTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
@@ -69,65 +68,40 @@
private Context mContext;
private WidgetsListSearchHeaderViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
- // Replace ActivityController with ActivityScenario, which is the recommended way for activity
- // testing.
- private ActivityController<TestActivity> mActivityController;
- private TestActivity mTestActivity;
@Mock
private IconCache mIconCache;
@Mock
- private DeviceProfile mDeviceProfile;
- @Mock
- private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- @Mock
private OnHeaderClickListener mOnHeaderClickListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
- mActivityController = Robolectric.buildActivity(TestActivity.class);
- mTestActivity = mActivityController.setup().get();
- mTestActivity.setDeviceProfile(mDeviceProfile);
-
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
-
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
- LayoutInflater.from(mTestActivity),
+ LayoutInflater.from(mContext),
mOnHeaderClickListener,
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
- }
-
- @After
- public void tearDown() {
- mActivityController.destroy();
+ new WidgetsListDrawableFactory(mContext));
}
@Test
public void bindViewHolder_appWith3Widgets_shouldShowTheCorrectAppNameAndSubtitle() {
WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
- new FrameLayout(mTestActivity));
+ new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
TextView appTitle = widgetsListHeader.findViewById(R.id.app_title);
TextView appSubtitle = widgetsListHeader.findViewById(R.id.app_subtitle);
@@ -139,14 +113,14 @@
@Test
public void bindViewHolder_shouldAttachOnHeaderClickListener() {
WidgetsListSearchHeaderHolder viewHolder = mViewHolderBinder.newViewHolder(
- new FrameLayout(mTestActivity));
+ new FrameLayout(mContext));
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
WidgetsListSearchHeaderEntry entry = generateSampleSearchHeader(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
widgetsListHeader.callOnClick();
verify(mOnHeaderClickListener).onHeaderClicked(eq(true),
@@ -165,14 +139,10 @@
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = cn;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(cn));
+ AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
similarity index 67%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
rename to tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 8f9d132..5816b77 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -15,13 +15,14 @@
*/
package com.android.launcher3.widget.picker;
-import static android.os.Looper.getMainLooper;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
-import static org.robolectric.Shadows.shadowOf;
+
+import static java.util.Collections.EMPTY_LIST;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -35,7 +36,9 @@
import android.widget.TableRow;
import android.widget.TextView;
-import com.android.launcher3.DeviceProfile;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
@@ -43,30 +46,24 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.testing.TestActivity;
-import com.android.launcher3.widget.CachingWidgetPreviewLoader;
-import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
+import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.android.controller.ActivityController;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsListTableViewHolderBinderTest {
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
@@ -74,10 +71,6 @@
private Context mContext;
private WidgetsListTableViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
- // Replace ActivityController with ActivityScenario, which is the recommended way for activity
- // testing.
- private ActivityController<TestActivity> mActivityController;
- private TestActivity mTestActivity;
@Mock
private OnLongClickListener mOnLongClickListener;
@@ -85,63 +78,42 @@
private OnClickListener mOnIconClickListener;
@Mock
private IconCache mIconCache;
- @Mock
- private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- @Mock
- private DeviceProfile mDeviceProfile;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = new ActivityContextWrapper(getApplicationContext());
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
- mActivityController = Robolectric.buildActivity(TestActivity.class);
- mTestActivity = mActivityController.setup().get();
- mTestActivity.setDeviceProfile(mDeviceProfile);
-
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- WidgetsListAdapter widgetsListAdapter = new WidgetsListAdapter(mContext,
- LayoutInflater.from(mTestActivity),
- mWidgetPreviewLoader,
- mIconCache,
- /* iconClickListener= */ view -> {},
- /* iconLongClickListener= */ view -> false);
mViewHolderBinder = new WidgetsListTableViewHolderBinder(
- LayoutInflater.from(mTestActivity),
+ LayoutInflater.from(mContext),
mOnIconClickListener,
mOnLongClickListener,
- new CachingWidgetPreviewLoader(mWidgetPreviewLoader),
- new WidgetsListDrawableFactory(mTestActivity),
- widgetsListAdapter);
- }
-
- @After
- public void tearDown() {
- mActivityController.destroy();
+ new WidgetsListDrawableFactory(mContext));
}
@Test
- public void bindViewHolder_appWith3Widgets_shouldHave3Widgets() {
+ public void bindViewHolder_appWith3Widgets_shouldHave3Widgets() throws Exception {
WidgetsRowViewHolder viewHolder = mViewHolderBinder.newViewHolder(
- new FrameLayout(mTestActivity));
+ new FrameLayout(mContext));
WidgetsListContentEntry entry = generateSampleAppWithWidgets(
APP_NAME,
TEST_PACKAGE,
/* numOfWidgets= */ 3);
- mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0);
- shadowOf(getMainLooper()).idle();
+ mViewHolderBinder.bindViewHolder(viewHolder, entry, /* position= */ 0, EMPTY_LIST);
+ Executors.MAIN_EXECUTOR.submit(() -> { }).get();
// THEN the table container has one row, which contains 3 widgets.
// View: .SampleWidget0 | .SampleWidget1 | .SampleWidget2
- assertThat(viewHolder.mTableContainer.getChildCount()).isEqualTo(1);
- TableRow row = (TableRow) viewHolder.mTableContainer.getChildAt(0);
+ assertThat(viewHolder.tableContainer.getChildCount()).isEqualTo(1);
+ TableRow row = (TableRow) viewHolder.tableContainer.getChildAt(0);
assertThat(row.getChildCount()).isEqualTo(3);
// Widget 0 label is .SampleWidget0.
assertWidgetCellWithLabel(row.getChildAt(0), ".SampleWidget0");
@@ -159,18 +131,15 @@
return new WidgetsListContentEntry(appInfo,
/* titleSectionName= */ "",
- generateWidgetItems(packageName, numOfWidgets));
+ generateWidgetItems(packageName, numOfWidgets),
+ Integer.MAX_VALUE);
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = cn;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(cn));
+ AppWidgetProviderInfo widgetInfo = WidgetUtils.createAppWidgetProviderInfo(cn);
widgetItems.add(new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
similarity index 92%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
rename to tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
index 106cac0..4b61b2c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
@@ -15,15 +15,20 @@
*/
package com.android.launcher3.widget.picker.model;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
-import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
-import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.ComponentWithLabel;
@@ -38,16 +43,13 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsListContentEntryTest {
private static final String PACKAGE_NAME = "com.android.test";
private static final String PACKAGE_NAME_2 = "com.android.test2";
@@ -60,7 +62,6 @@
@Mock private IconCache mIconCache;
- private Context mContext;
private InvariantDeviceProfile mTestProfile;
@Before
@@ -71,7 +72,6 @@
mWidgetsToLabels.put(mWidget2, "Dog");
mWidgetsToLabels.put(mWidget3, "Bird");
- mContext = RuntimeEnvironment.application;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
@@ -242,17 +242,12 @@
assertThat(widgetsListRowEntry1.equals(widgetsListRowEntry2)).isTrue();
}
-
private WidgetItem createWidgetItem(ComponentName componentName, int spanX, int spanY) {
String label = mWidgetsToLabels.get(componentName);
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = componentName;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(componentName));
+ AppWidgetProviderInfo widgetInfo = createAppWidgetProviderInfo(componentName);
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
- LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo);
+ LauncherAppWidgetProviderInfo.fromProviderInfo(getApplicationContext(), widgetInfo);
launcherAppWidgetProviderInfo.spanX = spanX;
launcherAppWidgetProviderInfo.spanY = spanY;
launcherAppWidgetProviderInfo.label = label;
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
similarity index 90%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
rename to tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
index 36b6f01..c862d6b 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -16,7 +16,10 @@
package com.android.launcher3.widget.picker.search;
-import static android.os.Looper.getMainLooper;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -25,7 +28,6 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
-import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -33,6 +35,9 @@
import android.graphics.Bitmap;
import android.os.UserHandle;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.ComponentWithLabel;
@@ -52,16 +57,13 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class SimpleWidgetsSearchAlgorithmTest {
@Mock private IconCache mIconCache;
@@ -82,7 +84,7 @@
private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doAnswer(invocation -> {
ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
@@ -91,7 +93,7 @@
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
- mContext = RuntimeEnvironment.application;
+ mContext = getApplicationContext();
mCalendarHeaderEntry =
createWidgetsHeaderEntry("com.example.android.Calendar", "Calendar", 2);
@@ -102,8 +104,8 @@
mClockHeaderEntry = createWidgetsHeaderEntry("com.example.android.Clock", "Clock", 3);
mClockContentEntry = createWidgetsContentEntry("com.example.android.Clock", "Clock", 3);
-
- mSimpleWidgetsSearchAlgorithm = new SimpleWidgetsSearchAlgorithm(mDataProvider);
+ mSimpleWidgetsSearchAlgorithm = MAIN_EXECUTOR.submit(
+ () -> new SimpleWidgetsSearchAlgorithm(mDataProvider)).get();
doReturn(Collections.EMPTY_LIST).when(mDataProvider).getAllWidgets();
}
@@ -156,13 +158,13 @@
}
@Test
- public void doSearch_shouldInformCallback() {
+ public void doSearch_shouldInformCallback() throws Exception {
doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
.when(mDataProvider)
.getAllWidgets();
mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback);
- shadowOf(getMainLooper()).idle();
+ MAIN_EXECUTOR.submit(() -> { }).get();
verify(mSearchCallback).onSearchResult(
matches("Ca"), argThat(a -> a != null && !a.isEmpty()));
}
@@ -195,14 +197,10 @@
}
private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
for (int i = 0; i < numOfWidgets; i++) {
ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
- AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
- widgetInfo.provider = cn;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(cn));
+ AppWidgetProviderInfo widgetInfo = createAppWidgetProviderInfo(cn);
WidgetItem widgetItem = new WidgetItem(
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
similarity index 83%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
rename to tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
index 7ac879a..583d37f 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
@@ -16,39 +16,38 @@
package com.android.launcher3.widget.picker.search;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.content.Context;
import android.view.View;
import android.widget.ImageButton;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.search.SearchAlgorithm;
-import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.controller.ActivityController;
import java.util.ArrayList;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public class WidgetsSearchBarControllerTest {
private WidgetsSearchBarController mController;
- // TODO: Replace ActivityController with ActivityScenario, which is the recommended way for
- // activity testing.
- private ActivityController<TestActivity> mActivityController;
private ExtendedEditText mEditText;
private ImageButton mCancelButton;
@Mock
@@ -59,20 +58,14 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mActivityController = Robolectric.buildActivity(TestActivity.class);
- TestActivity testActivity = mActivityController.setup().get();
+ Context context = getApplicationContext();
- mEditText = new ExtendedEditText(testActivity);
- mCancelButton = new ImageButton(testActivity);
+ mEditText = new ExtendedEditText(context);
+ mCancelButton = new ImageButton(context);
mController = new WidgetsSearchBarController(
mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener);
}
- @After
- public void tearDown() {
- mActivityController.destroy();
- }
-
@Test
public void onSearchResult_shouldInformSearchModeListener() {
ArrayList<WidgetsListBaseEntry> entries = new ArrayList<>();
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
similarity index 89%
rename from robolectric_tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
rename to tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
index 56d7d68..d6da776 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
@@ -15,11 +15,14 @@
*/
package com.android.launcher3.widget.picker.util;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
-import static org.robolectric.Shadows.shadowOf;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -29,6 +32,9 @@
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
@@ -42,15 +48,12 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
-@RunWith(RobolectricTestRunner.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class WidgetsTableUtilsTest {
private static final String TEST_PACKAGE = "com.google.test";
@@ -73,7 +76,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = getApplicationContext();
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
@@ -152,16 +155,14 @@
ArrayList<WidgetItem> widgetItems = new ArrayList<>();
widgetSizes.stream().forEach(
widgetSize -> {
- ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
- AppWidgetProviderInfo info = new AppWidgetProviderInfo();
- info.provider = ComponentName.createRelative(TEST_PACKAGE,
- ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y);
+ AppWidgetProviderInfo info = createAppWidgetProviderInfo(
+ ComponentName.createRelative(
+ TEST_PACKAGE,
+ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y));
LauncherAppWidgetProviderInfo widgetInfo =
LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
widgetInfo.spanX = widgetSize.x;
widgetInfo.spanY = widgetSize.y;
- ReflectionHelpers.setField(widgetInfo, "providerInfo",
- packageManager.addReceiverIfNotPresent(widgetInfo.provider));
widgetItems.add(new WidgetItem(widgetInfo, mTestProfile, mIconCache));
}
);
diff --git a/tests/src_common/README.md b/tests/src_common/README.md
deleted file mode 100644
index 2bc9e73..0000000
--- a/tests/src_common/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Common source code used by both android tests and robolectric tests
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index c2930f1..98d081b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -80,6 +80,7 @@
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -96,7 +97,7 @@
private static final String TAG = "Tapl";
private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
private static final int GESTURE_STEP_MS = 16;
- private static final long FORCE_PAUSE_TIMEOUT_MS = 700;
+ private static final long FORCE_PAUSE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2);
static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
@@ -247,11 +248,24 @@
if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
if (TestHelpers.isInLauncherProcess()) {
pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+ // b/195031154
+ SystemClock.sleep(5000);
} else {
try {
final int userId = ContextUtils.getUserId(getContext());
+ final String launcherPidCommand = "pidof " + pi.packageName;
+ final String initialPid = mDevice.executeShellCommand(launcherPidCommand)
+ .replaceAll("\\s", "");
mDevice.executeShellCommand(
"pm enable --user " + userId + " " + cn.flattenToString());
+ // Wait for Launcher restart after enabling test provider.
+ for (int i = 0; i < 100; ++i) {
+ final String currentPid = mDevice.executeShellCommand(launcherPidCommand)
+ .replaceAll("\\s", "");
+ if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break;
+ if (i == 99) fail("Launcher didn't restart after enabling test provider");
+ SystemClock.sleep(100);
+ }
} catch (IOException e) {
fail(e.toString());
}
@@ -288,6 +302,11 @@
}
Insets getTargetInsets() {
+ return getTestInfo(TestProtocol.REQUEST_TARGET_INSETS)
+ .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
+ Insets getWindowInsets() {
return getTestInfo(TestProtocol.REQUEST_WINDOW_INSETS)
.getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
@@ -299,7 +318,7 @@
public boolean isTwoPanels() {
return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS)
- .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
private void setForcePauseTimeout(long timeout) {
@@ -373,7 +392,7 @@
}
}
- private String getSystemAnomalyMessage() {
+ private String getSystemAnomalyMessage(boolean ignoreNavmodeChangeStates) {
try {
{
final StringBuilder sb = new StringBuilder();
@@ -396,16 +415,18 @@
if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
- final String visibleApps = mDevice.findObjects(getAnyObjectSelector())
- .stream()
- .map(LauncherInstrumentation::getApplicationPackageSafe)
- .distinct()
- .filter(pkg -> pkg != null)
- .collect(Collectors.joining(","));
- if (SYSTEMUI_PACKAGE.equals(visibleApps)) return "Only System UI views are visible";
+ if (!ignoreNavmodeChangeStates) {
+ final String visibleApps = mDevice.findObjects(getAnyObjectSelector())
+ .stream()
+ .map(LauncherInstrumentation::getApplicationPackageSafe)
+ .distinct()
+ .filter(pkg -> pkg != null)
+ .collect(Collectors.joining(","));
+ if (SYSTEMUI_PACKAGE.equals(visibleApps)) return "Only System UI views are visible";
- if (!mDevice.wait(Until.hasObject(getAnyObjectSelector()), WAIT_TIME_MS)) {
- return "Screen is empty";
+ if (!mDevice.wait(Until.hasObject(getAnyObjectSelector()), WAIT_TIME_MS)) {
+ return "Screen is empty";
+ }
}
final String navigationModeError = getNavigationModeMismatchError(true);
@@ -417,8 +438,12 @@
return null;
}
- public void checkForAnomaly() {
- final String systemAnomalyMessage = getSystemAnomalyMessage();
+ private void checkForAnomaly() {
+ checkForAnomaly(false);
+ }
+
+ public void checkForAnomaly(boolean ignoreNavmodeChangeStates) {
+ final String systemAnomalyMessage = getSystemAnomalyMessage(ignoreNavmodeChangeStates);
if (systemAnomalyMessage != null) {
Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
"http://go/tapl : Tests are broken by a non-Launcher system error: "
@@ -616,9 +641,7 @@
}
private String getNavigationButtonResPackage() {
- return isTablet() && getNavigationModel() == NavigationModel.THREE_BUTTON ?
- getLauncherPackageName() :
- SYSTEMUI_PACKAGE;
+ return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
}
private UiObject2 verifyContainerType(ContainerType containerType) {
@@ -749,11 +772,11 @@
boolean gestureStartFromLauncher = isTablet()
? !isLauncher3() || hasLauncherObject(WORKSPACE_RES_ID)
: isLauncherVisible();
- GestureScope gestureScope = gestureStartFromLauncher
- ? GestureScope.INSIDE_TO_OUTSIDE
- : GestureScope.OUTSIDE_WITH_PILFER;
if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
+ GestureScope gestureScope = gestureStartFromLauncher
+ ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
+ : GestureScope.OUTSIDE_WITH_PILFER;
linearGesture(
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
@@ -762,9 +785,6 @@
try (LauncherInstrumentation.Closable c1 = addContextLayer(
"Swiped up from context menu to home")) {
waitUntilLauncherObjectGone(CONTEXT_MENU_RES_ID);
- // Swiping up can temporarily bring Nexus Launcher if the current
- // Launcher is a Launcher3 one. Wait for the current launcher to reappear.
- SystemClock.sleep(5000); // b/187080582
waitForLauncherObject(getAnyObjectSelector());
}
}
@@ -779,7 +799,8 @@
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
- gestureScope);
+ gestureStartFromLauncher ? GestureScope.INSIDE_TO_OUTSIDE
+ : GestureScope.OUTSIDE_WITH_PILFER);
}
} else {
log("Hierarchy before clicking home:");
@@ -1014,6 +1035,15 @@
}
}
+ List<UiObject2> getChildren(UiObject2 container) {
+ try {
+ return container.getChildren();
+ } catch (StaleObjectException e) {
+ fail("The container disappeared from screen");
+ return null;
+ }
+ }
+
private boolean hasLauncherObject(String resId) {
return mDevice.hasObject(getLauncherObjectSelector(resId));
}
@@ -1133,7 +1163,7 @@
}
int getBottomGestureSize() {
- return Math.max(getTargetInsets().bottom, ResourceUtils.getNavbarSize(
+ return Math.max(getWindowInsets().bottom, ResourceUtils.getNavbarSize(
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources())) + 1;
}
@@ -1329,13 +1359,6 @@
}
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
- // b/190748682
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_UP:
- log("b/190748682: injecting " + event);
- break;
- }
assertTrue("injectInputEvent failed",
mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
event.recycle();
@@ -1527,7 +1550,7 @@
try {
return object.getVisibleBounds();
} catch (StaleObjectException e) {
- fail("Object " + object + " disappeared from screen");
+ fail("Object disappeared from screen");
return null;
} catch (Throwable t) {
fail(t.toString());
@@ -1538,6 +1561,7 @@
float getWindowCornerRadius() {
final Resources resources = getResources();
if (!supportsRoundedCornersOnWindows(resources)) {
+ Log.d(TAG, "No rounded corners");
return 0f;
}
@@ -1556,7 +1580,8 @@
// Always use the smallest radius to make sure the rounded corners will
// completely cover the display.
- return Math.min(topRadius, bottomRadius);
+ Log.d(TAG, "Rounded corners top: " + topRadius + " bottom: " + bottomRadius);
+ return Math.max(topRadius, bottomRadius);
}
private static boolean supportsRoundedCornersOnWindows(Resources resources) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 99d9889..6e7264a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -116,9 +116,9 @@
"widget_preview");
int i = 0;
for (; ; ) {
- final Collection<UiObject2> tableRows = widgetsContainer.getChildren();
+ final Collection<UiObject2> tableRows = mLauncher.getChildren(widgetsContainer);
for (UiObject2 row : tableRows) {
- final Collection<UiObject2> widgetCells = row.getChildren();
+ final Collection<UiObject2> widgetCells = mLauncher.getChildren(row);
for (UiObject2 widget : widgetCells) {
final UiObject2 label = mLauncher.findObjectInContainer(widget,
labelSelector);
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index db2e250..288c853 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -83,9 +83,9 @@
+ mLauncher.getTouchSlop());
mLauncher.swipeToState(
- 0,
+ windowCornerRadius,
startY,
- 0,
+ windowCornerRadius,
startY - swipeHeight - mLauncher.getTouchSlop(),
12,
ALL_APPS_STATE_ORDINAL, LauncherInstrumentation.GestureScope.INSIDE);