Merge "Lint fix: Invalid vector path in horizontal_ellipsis"
diff --git a/Android.mk b/Android.mk
index bc51ccd..f5ec7cd 100644
--- a/Android.mk
+++ b/Android.mk
@@ -29,47 +29,59 @@
include $(BUILD_PREBUILT)
#
-# Build rule for Launcher3 app.
+# Build rule for Launcher3 dependencies lib.
#
include $(CLEAR_VARS)
-
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-annotations
-
LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-compat \
- android-support-media-compat \
- android-support-core-utils \
- android-support-core-ui \
- android-support-fragment \
+ android-support-v4 \
android-support-v7-recyclerview \
android-support-dynamic-animation
LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, src_ui_overrides) \
- $(call all-java-files-under, src_flags) \
$(call all-proto-files-under, protos) \
$(call all-proto-files-under, proto_overrides)
-
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_PROGUARD_ENABLED := disabled
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MODULE := Launcher3CommonDepsLib
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_MANIFEST_FILE := AndroidManifest-common.xml
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+#
+# Build rule for Launcher3 app.
+#
+include $(CLEAR_VARS)
LOCAL_USE_AAPT2 := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, src_ui_overrides) \
+ $(call all-java-files-under, src_flags)
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3
LOCAL_PRIVILEGED_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2
+LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
@@ -81,100 +93,80 @@
# Build rule for Launcher3 Go app for Android Go devices.
#
include $(CLEAR_VARS)
-
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-annotations
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-compat \
- android-support-media-compat \
- android-support-core-utils \
- android-support-core-ui \
- android-support-fragment \
- android-support-v7-recyclerview \
- android-support-dynamic-animation
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_ui_overrides) \
- $(call all-java-files-under, go/src_flags) \
- $(call all-proto-files-under, protos) \
- $(call all-proto-files-under, proto_overrides)
+ $(call all-java-files-under, go/src_flags)
-LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/go/res \
- $(LOCAL_PATH)/res \
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
-LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
-
-LOCAL_USE_AAPT2 := true
-
LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3Go
LOCAL_PRIVILEGED_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
+LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/AndroidManifest.xml \
$(LOCAL_PATH)/AndroidManifest-common.xml
LOCAL_MANIFEST_FILE := go/AndroidManifest.xml
-
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-
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 := libSharedSystemUI
+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_flags)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SDK_VERSION := system_current
+LOCAL_MIN_SDK_VERSION := 26
+LOCAL_MODULE := Launcher3QuickStepLib
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+#
# Build rule for Quickstep app.
#
include $(CLEAR_VARS)
-
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-annotations \
- libSharedSystemUI
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-compat \
- android-support-media-compat \
- android-support-core-utils \
- android-support-core-ui \
- android-support-fragment \
- android-support-v7-recyclerview \
- android-support-dynamic-animation
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, quickstep/src) \
- $(call all-java-files-under, src_flags) \
- $(call all-proto-files-under, protos) \
- $(call all-proto-files-under, proto_overrides)
-
-LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/quickstep/res \
- $(LOCAL_PATH)/res \
-
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3QuickStepLib
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
-LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
-
-LOCAL_USE_AAPT2 := true
-
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
LOCAL_PACKAGE_NAME := Launcher3QuickStep
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_PRODUCT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
+LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/AndroidManifest.xml \
@@ -185,47 +177,34 @@
include $(BUILD_PACKAGE)
+
#
# Build rule for Launcher3 Go app with quickstep for Android Go devices.
#
include $(CLEAR_VARS)
-
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-v4 \
- android-support-v7-recyclerview \
- android-support-dynamic-animation \
- libSharedSystemUI
+LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI
+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, go/src_flags) \
- $(call all-proto-files-under, protos) \
- $(call all-proto-files-under, proto_overrides)
+ $(call all-java-files-under, go/src_flags)
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/quickstep/res \
- $(LOCAL_PATH)/go/res \
- $(LOCAL_PATH)/res \
- prebuilts/sdk/current/support/v7/recyclerview/res \
+ $(LOCAL_PATH)/go/res
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
-LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
-
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay \
- --extra-packages android.support.v7.recyclerview \
-
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
LOCAL_PRIVILEGED_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
+LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/go/AndroidManifest.xml \
@@ -234,7 +213,6 @@
LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
-
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b7c5793..3212980 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -56,7 +56,7 @@
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
- android:theme="@style/LauncherTheme"
+ android:theme="@style/AppTheme"
android:largeHeap="@bool/config_largeHeap"
android:restoreAnyVersion="true"
android:supportsRtl="true" >
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b2c5266..f58158f 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -52,6 +52,7 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Launcher3QuickStep)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..a8c84eb
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,4 @@
+adamcohen@google.com
+hyunyoungs@google.com
+sunnygoyal@google.com
+winsonc@google.com
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index fbaf981..0a9ad7b 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -31,7 +31,7 @@
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
- android:theme="@style/LauncherTheme"
+ android:theme="@style/AppTheme"
android:largeHeap="@bool/config_largeHeap"
android:restoreAnyVersion="true"
android:supportsRtl="true" >
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 778866d..cb74855 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -31,7 +31,7 @@
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
- android:theme="@style/LauncherTheme"
+ android:theme="@style/AppTheme"
android:largeHeap="@bool/config_largeHeap"
android:restoreAnyVersion="true"
android:supportsRtl="true" >
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 53a6ceb..27de1e9 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/drawable/ic_split_screen.xml b/quickstep/res/drawable/ic_split_screen.xml
index 77bd333..110af91 100644
--- a/quickstep/res/drawable/ic_split_screen.xml
+++ b/quickstep/res/drawable/ic_split_screen.xml
@@ -13,33 +13,16 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="88.0dp"
- android:height="88.0dp"
- android:viewportWidth="88.0"
- android:viewportHeight="88.0" >
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<path
- android:pathData="M 32,11
- C 32,11 68,11 68,11
- 76.74,11.06 76.98,12.76 77,21
- 77.01,25.97 78.50,38.23 73.85,40.98
- 71.80,42.19 68.35,42 66,42
- 66,42 22,42 22,42
- 18.82,41.99 14.87,42.38 12.60,39.69
- 10.71,37.44 11.01,33.77 11,31
- 10.99,25.54 9.53,16.08 13.31,12.02
- 18.07,10.21 26.66,11 32,11 Z
- M 32,46
- C 32,46 68,46 68,46
- 76.74,46.06 76.98,47.76 77,56
- 77.01,60.97 78.50,73.23 73.85,75.98
- 71.80,77.19 68.35,77 66,77
- 66,77 22,77 22,77
- 18.82,76.99 14.87,77.38 12.60,74.69
- 10.71,72.44 11.01,68.77 11,66
- 10.99,60.54 9.53,51.08 13.31,47.02
- 18.07,45.21 26.66,46 32,46 Z"
- android:fillColor="@android:color/white" />
-</vector>
\ No newline at end of file
+ android:fillColor="@android:color/white"
+ android:pathData="M18,4v5H6V4H18 M18,2H6C4.9,2,4,2.9,4,4v5c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C20,2.9,19.1,2,18,2L18,2z" />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,15v5H6v-5H18 M18,13H6c-1.1,0-2,0.9-2,2v5c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-5C20,13.9,19.1,13,18,13L18,13z" />
+</vector>
diff --git a/quickstep/res/drawable/task_menu_bg.xml b/quickstep/res/drawable/task_menu_bg.xml
new file mode 100644
index 0000000..d5597a9
--- /dev/null
+++ b/quickstep/res/drawable/task_menu_bg.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:gravity="bottom">
+ <!-- Shadow -->
+ <shape>
+ <gradient android:angle="270"
+ android:endColor="@android:color/transparent"
+ android:startColor="#26000000" />
+ <size android:height="@dimen/task_card_menu_shadow_height" />
+ </shape>
+ </item>
+ <item android:bottom="@dimen/task_card_menu_shadow_height">
+ <!-- Background -->
+ <shape>
+ <corners
+ android:topLeftRadius="@dimen/task_corner_radius"
+ android:topRightRadius="@dimen/task_corner_radius"
+ android:bottomLeftRadius="0dp"
+ android:bottomRightRadius="0dp" />
+ <solid android:color="?attr/popupColorPrimary" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index 84e13ad..ef272ed 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -20,25 +20,12 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
- <com.android.quickstep.views.RecentsViewContainer
- android:id="@+id/overview_panel_container"
+ <com.android.quickstep.fallback.FallbackRecentsView
+ android:id="@id/overview_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
- >
- <include layout="@layout/overview_clear_all_button"/>
-
- <com.android.quickstep.fallback.FallbackRecentsView
- android:id="@id/overview_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:outlineProvider="none"
- android:focusableInTouchMode="true"
- android:theme="@style/HomeScreenElementTheme"
- >
-
- </com.android.quickstep.fallback.FallbackRecentsView>
- </com.android.quickstep.views.RecentsViewContainer>
+ android:clipToPadding="false"
+ android:outlineProvider="none"
+ android:theme="@style/HomeScreenElementTheme" />
</com.android.quickstep.fallback.RecentsRootView>
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 25615e0..ea7a494 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -1,15 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
<com.android.quickstep.views.ClearAllButton
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/clear_all_button"
style="@android:style/Widget.DeviceDefault.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="start|top"
android:text="@string/recents_clear_all"
android:textColor="?attr/workspaceTextColor"
- android:visibility="invisible"
android:textSize="14sp"
- android:importantForAccessibility="no"
-/>
\ No newline at end of file
+ android:translationY="@dimen/task_thumbnail_half_top_margin"
+ />
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 840b040..7f1425b 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -14,26 +14,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.RecentsViewContainer
+<com.android.quickstep.views.LauncherRecentsView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
- android:visibility="invisible"
->
- <include layout="@layout/overview_clear_all_button"/>
-
- <com.android.quickstep.views.LauncherRecentsView
- android:id="@id/overview_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:outlineProvider="none"
- android:focusableInTouchMode="true"
- android:accessibilityPaneTitle="@string/accessibility_recent_apps"
- android:theme="@style/HomeScreenElementTheme"
- >
-
- </com.android.quickstep.views.LauncherRecentsView>
-</com.android.quickstep.views.RecentsViewContainer>
\ No newline at end of file
+ android:clipToPadding="false"
+ android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+ android:visibility="invisible" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index f163872..36d327d 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -17,8 +17,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:focusable="false"
- android:elevation="4dp">
+ android:defaultFocusHighlightEnabled="false"
+ android:elevation="4dp"
+ android:focusable="true">
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
@@ -30,7 +31,7 @@
android:id="@+id/icon"
android:layout_width="@dimen/task_thumbnail_icon_size"
android:layout_height="@dimen/task_thumbnail_icon_size"
- android:importantForAccessibility="no"
+ android:layout_gravity="top|center_horizontal"
android:focusable="false"
- android:layout_gravity="top|center_horizontal" />
+ android:importantForAccessibility="no" />
</com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index b846665..bf55ece 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -16,21 +16,31 @@
-->
<com.android.quickstep.views.TaskMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/bg_popup_item_width"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="invisible"
- android:elevation="@dimen/deep_shortcuts_elevation"
+ android:animateLayoutChanges="true"
+ android:background="@drawable/task_menu_bg"
+ android:paddingBottom="@dimen/task_card_menu_shadow_height"
android:orientation="vertical"
- android:background="?attr/popupColorPrimary"
- android:divider="@drawable/all_apps_divider"
- android:showDividers="middle"
- android:animateLayoutChanges="true">
- <TextView
- android:id="@+id/task_icon_and_name"
- android:layout_width="match_parent"
- android:layout_height="112dp"
- android:textSize="14sp"
- android:paddingTop="18dp"
- android:drawablePadding="8dp"
- android:gravity="center_horizontal"/>
+ android:visibility="invisible">
+
+ <TextView
+ android:id="@+id/task_icon_and_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
+ android:gravity="center_horizontal"
+ android:layout_marginBottom="16dp"
+ android:textSize="12sp"/>
+
+ <LinearLayout
+ android:id="@+id/menu_option_layout"
+ style="@style/TaskMenu"
+ android:divider="@drawable/all_apps_divider"
+ android:showDividers="beginning"
+ android:paddingStart="@dimen/task_card_menu_horizontal_padding"
+ android:paddingEnd="@dimen/task_card_menu_horizontal_padding"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
</com.android.quickstep.views.TaskMenuView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_view_menu_option.xml b/quickstep/res/layout/task_view_menu_option.xml
new file mode 100644
index 0000000..102ae9b
--- /dev/null
+++ b/quickstep/res/layout/task_view_menu_option.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/TaskMenu.Option"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/task_card_menu_option_vertical_padding"
+ android:paddingBottom="@dimen/task_card_menu_option_vertical_padding"
+ android:background="?android:attr/selectableItemBackground"
+ android:theme="@style/PopupItem" >
+
+ <View
+ android:id="@+id/icon"
+ android:layout_width="@dimen/system_shortcut_icon_size"
+ android:layout_height="@dimen/system_shortcut_icon_size"
+ android:layout_marginTop="@dimen/system_shortcut_header_icon_padding"
+ android:layout_marginBottom="@dimen/deep_shortcut_drawable_padding"
+ android:layout_gravity="center_horizontal"
+ android:backgroundTint="?android:attr/textColorTertiary"/>
+
+ <TextView
+ style="@style/BaseIcon"
+ android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/popup_padding_end"
+ android:textSize="12sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="sans-serif"
+ android:gravity="center_horizontal"
+ android:layout_gravity="center_horizontal"
+ android:focusable="false" />
+
+</LinearLayout>
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
new file mode 100644
index 0000000..c03eaa2
--- /dev/null
+++ b/quickstep/res/values-land/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+ <dimen name="task_card_menu_horizontal_padding">24dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-land/styles.xml b/quickstep/res/values-land/styles.xml
new file mode 100644
index 0000000..0824b4f
--- /dev/null
+++ b/quickstep/res/values-land/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+ <!-- Task Menu layout styles. -->
+ <style name="TaskMenu">
+ <item name="android:orientation">horizontal</item>
+ </style>
+
+ <!-- Task Menu Option layout styles. -->
+ <style name="TaskMenu.Option">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index ed18bf5..49c4492 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -17,10 +17,11 @@
<resources>
<dimen name="task_thumbnail_top_margin">24dp</dimen>
+ <dimen name="task_thumbnail_half_top_margin">12dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
- <dimen name="task_menu_background_radius">12dp</dimen>
<dimen name="task_corner_radius">2dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
+ <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
<dimen name="quickscrub_adjacent_visible_width">20dp</dimen>
<!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
@@ -41,6 +42,9 @@
<!-- Total space (start + end) between the task card and the edge of the screen
in various configurations -->
<dimen name="task_card_vert_space">40dp</dimen>
+ <dimen name="task_card_menu_option_vertical_padding">8dp</dimen>
+ <dimen name="task_card_menu_shadow_height">3dp</dimen>
+ <dimen name="task_card_menu_horizontal_padding">0dp</dimen>
<dimen name="portrait_task_card_horz_space">136dp</dimen>
<dimen name="landscape_task_card_horz_space">200dp</dimen>
<dimen name="multi_window_task_card_horz_space">100dp</dimen>
@@ -48,8 +52,7 @@
docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
<dimen name="multi_window_task_divider_size">10dp</dimen>
- <!-- Width of the space behind the last task in Overview. In the center of it, there is "Clear all" button. -->
- <dimen name="clear_all_container_width">168dp</dimen>
-
<dimen name="shelf_surface_radius">16dp</dimen>
+ <!-- same as vertical_drag_handle_size -->
+ <dimen name="shelf_surface_offset">24dp</dimen>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
new file mode 100644
index 0000000..bb364ff
--- /dev/null
+++ b/quickstep/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+ <!-- Task Menu layout styles. -->
+ <style name="TaskMenu">
+ <item name="android:orientation">vertical</item>
+ </style>
+
+ <!-- Task Menu Option layout styles. -->
+ <style name="TaskMenu.Option">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 2630edb..14633af 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -18,6 +18,8 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
+import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
+import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -31,8 +33,6 @@
import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
-import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
-import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -53,9 +53,7 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
import android.util.Pair;
-import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
@@ -72,6 +70,7 @@
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityCompat;
@@ -80,7 +79,8 @@
import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.TransactionCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams;
import com.android.systemui.shared.system.WindowManagerWrapper;
/**
@@ -92,8 +92,16 @@
implements OnDeviceProfileChangeListener {
private static final String TAG = "LauncherTransition";
+
+ /** Duration of status bar animations. */
public static final int STATUS_BAR_TRANSITION_DURATION = 120;
+ /**
+ * Since our animations decelerate heavily when finishing, we want to start status bar animations
+ * x ms before the ending.
+ */
+ public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
+
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
@@ -187,7 +195,7 @@
mLauncher.getStateManager().setCurrentAnimation(anim);
Rect windowTargetBounds = getWindowTargetBounds(targetCompats);
- anim.play(getIconAnimator(v, windowTargetBounds));
+ playIconAnimators(anim, v, windowTargetBounds);
if (launcherClosing) {
Pair<AnimatorSet, Runnable> launcherContentAnimator =
getLauncherContentAnimator(true /* isAppOpening */);
@@ -210,9 +218,14 @@
}
};
- int duration = findTaskViewToLaunch(launcher, v, null) != null
- ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
- int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
+ boolean fromRecents = mLauncher.getStateManager().getState().overviewUi
+ && findTaskViewToLaunch(launcher, v, null) != null;
+ int duration = fromRecents
+ ? RECENTS_LAUNCH_DURATION
+ : APP_LAUNCH_DURATION;
+
+ int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
+ - STATUS_BAR_TRANSITION_PRE_DELAY;
return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
runner, duration, statusBarTransitionDelay));
}
@@ -369,8 +382,9 @@
launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
- View overview = mLauncher.getOverviewPanelContainer();
- ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, View.ALPHA, alphas);
+ RecentsView overview = mLauncher.getOverviewPanel();
+ ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+ RecentsView.CONTENT_ALPHA, alphas);
alpha.setDuration(217);
alpha.setInterpolator(LINEAR);
launcherAnimator.play(alpha);
@@ -380,14 +394,7 @@
transY.setDuration(350);
launcherAnimator.play(transY);
- overview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-
- endListener = () -> {
- overview.setLayerType(View.LAYER_TYPE_NONE, null);
- overview.setAlpha(1f);
- overview.setTranslationY(0f);
- mLauncher.getStateManager().reapplyState();
- };
+ endListener = mLauncher.getStateManager()::reapplyState;
} else {
mDragLayerAlpha.setValue(alphas[0]);
ObjectAnimator alpha =
@@ -413,9 +420,9 @@
}
/**
- * @return Animator that controls the icon used to launch the target.
+ * Animators for the "floating view" of the view used to launch the target.
*/
- private AnimatorSet getIconAnimator(View v, Rect windowTargetBounds) {
+ private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds) {
final boolean isBubbleTextView = v instanceof BubbleTextView;
mFloatingView = new View(mLauncher);
if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
@@ -470,7 +477,6 @@
((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
v.setVisibility(View.INVISIBLE);
- AnimatorSet appIconAnimatorSet = new AnimatorSet();
int[] dragLayerBounds = new int[2];
mDragLayer.getLocationOnScreen(dragLayerBounds);
@@ -500,8 +506,8 @@
}
x.setInterpolator(AGGRESSIVE_EASE);
y.setInterpolator(AGGRESSIVE_EASE);
- appIconAnimatorSet.play(x);
- appIconAnimatorSet.play(y);
+ appOpenAnimator.play(x);
+ appOpenAnimator.play(y);
// Scale the app icon to take up the entire screen. This simplifies the math when
// animating the app window position / scale.
@@ -512,7 +518,7 @@
.ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
scaleAnim.setDuration(APP_LAUNCH_DURATION)
.setInterpolator(Interpolators.EXAGGERATED_EASE);
- appIconAnimatorSet.play(scaleAnim);
+ appOpenAnimator.play(scaleAnim);
// Fade out the app icon.
ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
@@ -525,9 +531,9 @@
alpha.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_ALPHA_DURATION));
}
alpha.setInterpolator(LINEAR);
- appIconAnimatorSet.play(alpha);
+ appOpenAnimator.play(alpha);
- appIconAnimatorSet.addListener(new AnimatorListenerAdapter() {
+ appOpenAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Reset launcher to normal state
@@ -535,7 +541,6 @@
((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
}
});
- return appIconAnimatorSet;
}
/**
@@ -558,22 +563,21 @@
Rect crop = new Rect();
Matrix matrix = new Matrix();
+ RemoteAnimationTargetSet openingTargets = new RemoteAnimationTargetSet(targets,
+ MODE_OPENING);
+ RemoteAnimationTargetSet closingTargets = new RemoteAnimationTargetSet(targets,
+ MODE_CLOSING);
+ SyncRtSurfaceTransactionApplier surfaceApplier = new SyncRtSurfaceTransactionApplier(
+ mFloatingView);
+
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(APP_LAUNCH_DURATION);
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
// Fade alpha for the app window.
FloatProp mAlpha = new FloatProp(0f, 1f, 0, 60, LINEAR);
- boolean isFirstFrame = true;
@Override
public void onUpdate(float percent) {
- final Surface surface = getSurface(mFloatingView);
- final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
- if (frameNumber == -1) {
- // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
- Log.w(TAG, "Failed to animate, surface got destroyed.");
- return;
- }
final float easePercent = AGGRESSIVE_EASE.getInterpolation(percent);
// Calculate app icon size.
@@ -584,7 +588,6 @@
float scaleX = iconWidth / windowTargetBounds.width();
float scaleY = iconHeight / windowTargetBounds.height();
float scale = Math.min(1f, Math.min(scaleX, scaleY));
- matrix.setScale(scale, scale);
// Position the scaled window on top of the icon
int windowWidth = windowTargetBounds.width();
@@ -598,7 +601,6 @@
float transX0 = floatingViewBounds[0] - offsetX;
float transY0 = floatingViewBounds[1] - offsetY;
- matrix.postTranslate(transX0, transY0);
// Animate the window crop so that it starts off as a square, and then reveals
// horizontally.
@@ -609,23 +611,27 @@
crop.right = windowWidth;
crop.bottom = (int) (crop.top + cropHeight);
- TransactionCompat t = new TransactionCompat();
- if (isFirstFrame) {
- RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_OPENING);
- isFirstFrame = false;
- }
- for (RemoteAnimationTargetCompat target : targets) {
- if (target.mode == MODE_OPENING) {
- t.setAlpha(target.leash, mAlpha.value);
- t.setMatrix(target.leash, matrix);
- t.setWindowCrop(target.leash, crop);
- t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
- }
- }
- t.setEarlyWakeup();
- t.apply();
+ SurfaceParams[] params = new SurfaceParams[targets.length];
+ for (int i = targets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = targets[i];
- matrix.reset();
+ Rect targetCrop;
+ float alpha;
+ if (target.mode == MODE_OPENING) {
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(transX0, transY0);
+ targetCrop = crop;
+ alpha = mAlpha.value;
+ } else {
+ matrix.setTranslate(target.position.x, target.position.y);
+ alpha = 1f;
+ targetCrop = target.sourceContainerBounds;
+ }
+
+ params[i] = new SurfaceParams(target.leash, alpha, matrix, targetCrop,
+ RemoteAnimationProvider.getLayer(target, MODE_OPENING));
+ }
+ surfaceApplier.scheduleApply(params);
}
});
return appAnimator;
@@ -669,6 +675,11 @@
return;
}
+ if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
+ mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
+ mLauncher.getStateManager().moveToRestState();
+ }
+
AnimatorSet anim = null;
RemoteAnimationProvider provider = mRemoteAnimationProvider;
if (provider != null) {
@@ -705,6 +716,8 @@
* Animator that controls the transformations of the windows the targets that are closing.
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
+ SyncRtSurfaceTransactionApplier surfaceApplier =
+ new SyncRtSurfaceTransactionApplier(mDragLayer);
Matrix matrix = new Matrix();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
@@ -714,30 +727,28 @@
FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
- boolean isFirstFrame = true;
-
@Override
public void onUpdate(float percent) {
- TransactionCompat t = new TransactionCompat();
- if (isFirstFrame) {
- RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_CLOSING);
- isFirstFrame = false;
- }
- for (RemoteAnimationTargetCompat app : targets) {
- if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
- t.setAlpha(app.leash, mAlpha.value);
+ SurfaceParams[] params = new SurfaceParams[targets.length];
+ for (int i = targets.length - 1; i >= 0; i--) {
+ RemoteAnimationTargetCompat target = targets[i];
+ float alpha;
+ if (target.mode == MODE_CLOSING) {
matrix.setScale(mScale.value, mScale.value,
- app.sourceContainerBounds.centerX(),
- app.sourceContainerBounds.centerY());
+ target.sourceContainerBounds.centerX(),
+ target.sourceContainerBounds.centerY());
matrix.postTranslate(0, mDy.value);
- matrix.postTranslate(app.position.x, app.position.y);
- t.setMatrix(app.leash, matrix);
+ matrix.postTranslate(target.position.x, target.position.y);
+ alpha = mAlpha.value;
+ } else {
+ matrix.setTranslate(target.position.x, target.position.y);
+ alpha = 1f;
}
+ params[i] = new SurfaceParams(target.leash, alpha, matrix,
+ target.sourceContainerBounds,
+ RemoteAnimationProvider.getLayer(target, MODE_CLOSING));
}
- t.setEarlyWakeup();
- t.apply();
-
- matrix.reset();
+ surfaceApplier.scheduleApply(params);
}
});
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 2e6dcc0..722f51b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -16,6 +16,8 @@
package com.android.launcher3.uioverrides;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import com.android.launcher3.Launcher;
@@ -56,6 +58,13 @@
final float alpha = (float) valueAnimator.getAnimatedValue();
mOverviewInteractionState.setBackButtonAlpha(alpha, false);
});
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Reapply the final alpha in case some state (e.g. window focus) changed.
+ UiFactory.onLauncherStateOrFocusChanged(mLauncher);
+ }
+ });
builder.play(anim);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 9169ffb..7f956f8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -25,9 +25,11 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
/**
@@ -75,6 +77,7 @@
public void onStateDisabled(Launcher launcher) {
RecentsView rv = launcher.getOverviewPanel();
rv.setOverviewStateEnabled(false);
+ RecentsModel.getInstance(launcher).resetAssistCache();
}
@Override
@@ -118,6 +121,11 @@
/ launcher.getAllAppsController().getShiftRange());
}
+ @Override
+ public String getDescription(Launcher launcher) {
+ return launcher.getString(R.string.accessibility_desc_recent_apps);
+ }
+
public static float getDefaultSwipeHeight(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 3fb7cd4..0eead88 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -18,20 +18,24 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
-import android.view.animation.OvershootInterpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
@@ -51,6 +55,16 @@
private static final String TAG = "PortraitStatesTouchCtrl";
+ /**
+ * The progress at which all apps content will be fully visible when swiping up from overview.
+ */
+ private static final float ALL_APPS_CONTENT_FADE_THRESHOLD = 0.08f;
+
+ /**
+ * The progress at which recents will begin fading out when swiping up from overview.
+ */
+ private static final float RECENTS_FADE_THRESHOLD = 0.88f;
+
private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
// If true, we will finish the current animation instantly on second touch.
@@ -69,8 +83,18 @@
mCurrentAnimation.getAnimationPlayer().end();
}
- // If we are already animating from a previous state, we can intercept.
- return true;
+ AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+ if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress()) {
+ // If we are already animating from a previous state, we can intercept as long as
+ // the touch is below the current all apps progress (to allow for double swipe).
+ return true;
+ }
+ // Otherwise, make sure everything is settled and don't intercept so they can scroll
+ // recents, dismiss a task, etc.
+ if (mAtomicAnim != null) {
+ mAtomicAnim.end();
+ }
+ return false;
}
if (mLauncher.isInState(ALL_APPS)) {
// In all-apps only listen if the container cannot scroll itself
@@ -115,7 +139,38 @@
AnimatorSetBuilder builder = new AnimatorSetBuilder();
builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
+ return builder;
+ }
+ public static AnimatorSetBuilder getOverviewToAllAppsAnimation() {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
+ 0, ALL_APPS_CONTENT_FADE_THRESHOLD));
+ builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(DEACCEL,
+ RECENTS_FADE_THRESHOLD, 1));
+ return builder;
+ }
+
+ private AnimatorSetBuilder getAllAppsToOverviewAnimation() {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
+ 1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
+ builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(ACCEL,
+ 0f, 1 - RECENTS_FADE_THRESHOLD));
+ return builder;
+ }
+
+ @Override
+ protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
+ LauncherState toState) {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ if (fromState == NORMAL && toState == OVERVIEW) {
+ builder = getNormalToOverviewAnimation();
+ } else if (fromState == OVERVIEW && toState == ALL_APPS) {
+ builder = getOverviewToAllAppsAnimation();
+ } else if (fromState == ALL_APPS && toState == OVERVIEW) {
+ builder = getAllAppsToOverviewAnimation();
+ }
return builder;
}
@@ -129,20 +184,17 @@
float totalShift = endVerticalShift - startVerticalShift;
- final AnimatorSetBuilder builder;
-
- if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
- builder = getNormalToOverviewAnimation();
- } else {
- builder = new AnimatorSetBuilder();
- }
+ final AnimatorSetBuilder builder = totalShift == 0 ? new AnimatorSetBuilder()
+ : getAnimatorSetBuilderForStates(mFromState, mToState);
cancelPendingAnim();
RecentsView recentsView = mLauncher.getOverviewPanel();
- TaskView taskView = (TaskView) recentsView.getChildAt(recentsView.getNextPage());
+ TaskView taskView = recentsView.getTaskViewAt(recentsView.getNextPage());
if (recentsView.shouldSwipeDownLaunchApp() && mFromState == OVERVIEW && mToState == NORMAL
&& taskView != null) {
+ // Reset the state manager, when changing the interaction mode
+ mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
@@ -190,7 +242,7 @@
// Update all apps interpolator to add a bit of overshoot starting from currFraction
final float currFraction = mCurrentAnimation.getProgressFraction();
mAllAppsInterpolatorWrapper.baseInterpolator = Interpolators.clampToProgress(
- new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)), currFraction, 1);
+ Interpolators.overshootInterpolatorForVelocity(velocity), currFraction, 1);
animator.setDuration(Math.min(expectedDuration, ATOMIC_DURATION))
.setInterpolator(LINEAR);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index ea27eb2..abd2846 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -25,7 +25,7 @@
import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR;
import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
-import static com.android.quickstep.views.RecentsViewContainer.CONTENT_ALPHA;
+import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
@@ -40,24 +40,21 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertySetter;
import com.android.quickstep.views.LauncherRecentsView;
-import com.android.quickstep.views.RecentsViewContainer;
@TargetApi(Build.VERSION_CODES.O)
public class RecentsViewStateController implements StateHandler {
private final Launcher mLauncher;
private final LauncherRecentsView mRecentsView;
- private final RecentsViewContainer mRecentsViewContainer;
public RecentsViewStateController(Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
- mRecentsViewContainer = launcher.getOverviewPanelContainer();
}
@Override
public void setState(LauncherState state) {
- mRecentsViewContainer.setContentAlpha(state.overviewUi ? 1 : 0);
+ mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
mRecentsView.setTranslationYFactor(scaleTranslationYFactor[1]);
@@ -86,7 +83,7 @@
scaleAndTransYInterpolator);
setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
scaleAndTransYInterpolator);
- setter.setFloat(mRecentsViewContainer, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
+ setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
if (!toState.overviewUi) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index a405735..cfd4119 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -115,8 +115,8 @@
} else {
mTaskBeingDragged = null;
- for (int i = 0; i < mRecentsView.getChildCount(); i++) {
- TaskView view = mRecentsView.getPageAt(i);
+ for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
+ TaskView view = mRecentsView.getTaskViewAt(i);
if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
.isEventOverView(view, ev)) {
mTaskBeingDragged = view;
@@ -241,16 +241,16 @@
if (blockedFling) {
fling = false;
}
+ float progress = mCurrentAnimation.getProgressFraction();
+ float interpolatedProgress = mCurrentAnimation.getInterpolator().getInterpolation(progress);
if (fling) {
logAction = Touch.FLING;
boolean goingUp = velocity < 0;
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
logAction = Touch.SWIPE;
- goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
+ goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
}
-
- float progress = mCurrentAnimation.getProgressFraction();
long animationDuration = SwipeDetector.calculateDuration(
velocity, goingToEnd ? (1 - progress) : progress);
if (blockedFling && !goingToEnd) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index dd5dcbe..2d0946b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -25,7 +25,6 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
@@ -46,7 +45,6 @@
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.RecentsModel;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityCompat;
@@ -167,11 +165,20 @@
}
}
+ public static void onEnterAnimationComplete(Context context) {
+ // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+ // as a part of quickstep/scrub, so that high-res thumbnails can load the next time we
+ // enter overview
+ RecentsModel.getInstance(context).getRecentsTaskLoader()
+ .getHighResThumbnailLoader().setVisible(true);
+ }
+
public static void onLauncherStateOrResumeChanged(Launcher launcher) {
LauncherState state = launcher.getStateManager().getState();
DeviceProfile profile = launcher.getDeviceProfile();
WindowManagerWrapper.getInstance().setShelfHeight(
- state != ALL_APPS && launcher.isUserActive() && !profile.isVerticalBarLayout(),
+ (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+ && !profile.isVerticalBarLayout(),
profile.hotseatBarSizePx);
if (state == NORMAL) {
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 52a6dd5..ced8754 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -15,16 +15,21 @@
*/
package com.android.quickstep;
+import static android.view.View.TRANSLATION_Y;
+
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
+import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
@@ -49,20 +54,21 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.uioverrides.FastOverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.TransformedRect;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.TransformedRect;
import com.android.quickstep.views.LauncherLayoutListener;
-import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.Objects;
@@ -179,9 +185,11 @@
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
- return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
+ return dp.hotseatBarSizePx + hotseatInset;
} else {
- return dp.heightPx - outRect.rect.bottom;
+ int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
+ // Track slightly below the top of the shelf (between top and content).
+ return shelfHeight - dp.edgeMarginPx * 2;
}
}
@@ -243,34 +251,68 @@
if (wasVisible) {
DeviceProfile dp = activity.getDeviceProfile();
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
- activity.getStateManager().goToState(startState, false);
callback.accept(activity.getStateManager()
- .createAnimationToNewWorkspace(endState, accuracy));
+ .createAnimationToNewWorkspace(startState, endState, accuracy));
return;
}
- if (activity.getDeviceProfile().isVerticalBarLayout()) {
- return;
- }
-
- AllAppsTransitionController controller = activity.getAllAppsController();
AnimatorSet anim = new AnimatorSet();
- float scrollRange = Math.max(controller.getShiftRange(), 1);
- float progressDelta = (transitionLength / scrollRange);
+ if (!activity.getDeviceProfile().isVerticalBarLayout()) {
+ AllAppsTransitionController controller = activity.getAllAppsController();
+ float scrollRange = Math.max(controller.getShiftRange(), 1);
+ float progressDelta = (transitionLength / scrollRange);
- float endProgress = endState.getVerticalProgress(activity);
- float startProgress = endProgress + progressDelta;
- ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
- controller, ALL_APPS_PROGRESS, startProgress, endProgress);
- shiftAnim.setInterpolator(LINEAR);
- anim.play(shiftAnim);
+ float endProgress = endState.getVerticalProgress(activity);
+ float startProgress = endProgress + progressDelta;
+ ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
+ controller, ALL_APPS_PROGRESS, startProgress, endProgress);
+ shiftAnim.setInterpolator(LINEAR);
+ anim.play(shiftAnim);
+
+ // Since we are changing the start position of the UI, reapply the state, at the end
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ activity.getStateManager().reapplyState();
+ }
+ });
+ }
+
+ if (interactionType == INTERACTION_NORMAL) {
+ playScaleDownAnim(anim, activity);
+ }
anim.setDuration(transitionLength * 2);
activity.getStateManager().setCurrentAnimation(anim);
callback.accept(AnimatorPlaybackController.wrap(anim, transitionLength * 2));
}
+ /**
+ * Scale down recents from the center task being full screen to being in overview.
+ */
+ private void playScaleDownAnim(AnimatorSet anim, Launcher launcher) {
+ RecentsView recentsView = launcher.getOverviewPanel();
+ TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+ if (v == null) {
+ return;
+ }
+ ClipAnimationHelper clipHelper = new ClipAnimationHelper();
+ clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
+ if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) {
+ float fromScale = clipHelper.getSourceRect().width()
+ / clipHelper.getTargetRect().width();
+ float fromTranslationY = clipHelper.getSourceRect().centerY()
+ - clipHelper.getTargetRect().centerY();
+ Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1);
+ Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
+ fromTranslationY, 0);
+ scale.setInterpolator(LINEAR);
+ translateY.setInterpolator(LINEAR);
+ anim.playTogether(scale, translateY);
+ }
+ }
+
@Override
public ActivityInitListener createActivityInitListener(
BiPredicate<Launcher, Boolean> onInitListener) {
@@ -409,7 +451,7 @@
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
- return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
+ return dp.hotseatBarSizePx + hotseatInset;
} else {
return dp.heightPx - outRect.rect.bottom;
}
@@ -427,7 +469,7 @@
return (transitionLength, interactionType) -> { };
}
- RecentsViewContainer rv = activity.getOverviewPanelContainer();
+ RecentsView rv = activity.getOverviewPanel();
rv.setContentAlpha(0);
return new AnimationFactory() {
@@ -451,8 +493,7 @@
return;
}
- ObjectAnimator anim = ObjectAnimator
- .ofFloat(rv, RecentsViewContainer.CONTENT_ALPHA, 0, 1);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
anim.setDuration(transitionLength).setInterpolator(LINEAR);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(anim);
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
index fbcde8b..6b66ec8 100644
--- a/quickstep/src/com/android/quickstep/LongSwipeHelper.java
+++ b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
@@ -20,18 +20,23 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
-import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
-import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_OVERSHOOT_DURATION;
import android.animation.ValueAnimator;
-import android.view.Surface;
+import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.Interpolators.OvershootParams;
+import com.android.launcher3.uioverrides.PortraitStatesTouchController;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -39,7 +44,6 @@
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.TransactionCompat;
/**
* Utility class to handle long swipe from an app.
@@ -65,15 +69,16 @@
}
private void init() {
- setTargetAlpha(0, true);
mFlingBlockCheck.blockFling();
// Init animations
AllAppsTransitionController controller = mLauncher.getAllAppsController();
// TODO: Scale it down so that we can reach all-apps in screen space
mMaxSwipeDistance = Math.max(1, controller.getProgress() * controller.getShiftRange());
- mAnimator = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(ALL_APPS, Math.round(2 * mMaxSwipeDistance));
+
+ AnimatorSetBuilder builder = PortraitStatesTouchController.getOverviewToAllAppsAnimation();
+ mAnimator = mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, builder,
+ Math.round(2 * mMaxSwipeDistance), null, LauncherStateManager.ANIM_ALL);
mAnimator.dispatchOnStart();
}
@@ -83,14 +88,15 @@
}
public void destroy() {
- // TODO: We can probably also hide the task view
- setTargetAlpha(1, false);
+ // TODO: We can probably also show the task view
mLauncher.getStateManager().goToState(OVERVIEW, false);
}
public void end(float velocity, boolean isFling, Runnable callback) {
+ float velocityPxPerMs = velocity / 1000;
long duration = MAX_SWIPE_DURATION;
+ Interpolator interpolator = DEACCEL;
final float currentFraction = mAnimator.getProgressFraction();
final boolean toAllApps;
@@ -108,6 +114,16 @@
long expectedDuration = Math.abs(Math.round((endProgress - currentFraction)
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
+
+ if (blockedFling && !toAllApps) {
+ Interpolators.OvershootParams overshoot = new OvershootParams(currentFraction,
+ currentFraction, endProgress, velocityPxPerMs, (int) mMaxSwipeDistance);
+ duration = (overshoot.duration + duration);
+ duration = Utilities.boundToRange(duration, MIN_OVERSHOOT_DURATION,
+ MAX_SWIPE_DURATION);
+ interpolator = overshoot.interpolator;
+ endProgress = overshoot.end;
+ }
} else {
toAllApps = velocity < 0;
endProgress = toAllApps ? 1 : 0;
@@ -120,47 +136,20 @@
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
- long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity));
+ long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
- if (blockedFling && !toAllApps) {
- duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
- }
final boolean finalIsFling = isFling;
mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
+
ValueAnimator animator = mAnimator.getAnimationPlayer();
- animator.setDuration(duration).setInterpolator(DEACCEL);
+ animator.setDuration(duration).setInterpolator(interpolator);
animator.setFloatValues(currentFraction, endProgress);
animator.start();
}
- private void setTargetAlpha(float alpha, boolean defer) {
- final Surface surface = getSurface(mLauncher.getDragLayer());
- final long frameNumber = defer && surface != null ? getNextFrameNumber(surface) : -1;
- if (defer) {
- if (frameNumber == -1) {
- defer = false;
- } else {
- mLauncher.getDragLayer().invalidate();
- }
- }
-
- TransactionCompat transaction = new TransactionCompat();
- for (RemoteAnimationTargetCompat app : mTargetSet.apps) {
- if (!(app.isNotInRecents
- || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
- transaction.setAlpha(app.leash, alpha);
- if (defer) {
- transaction.deferTransactionUntil(app.leash, surface, frameNumber);
- }
- }
- }
- transaction.setEarlyWakeup();
- transaction.apply();
- }
-
private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) {
mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
if (!toAllApps) {
@@ -176,4 +165,12 @@
callback.run();
}
+
+ public float getTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
+ if (!(app.isNotInRecents
+ || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
+ return 0;
+ }
+ return expectedAlpha;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index c856282..0e811f7 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -78,6 +78,7 @@
private final MainThreadExecutor mMainThreadExecutor;
private final Choreographer mBackgroundThreadChoreographer;
private final OverviewCallbacks mOverviewCallbacks;
+ private final TaskOverlayFactory mTaskOverlayFactory;
private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
@@ -99,7 +100,7 @@
RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
@HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
- VelocityTracker velocityTracker) {
+ TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker) {
super(base);
mRunningTask = runningTaskInfo;
@@ -111,6 +112,7 @@
mBackgroundThreadChoreographer = backgroundThreadChoreographer;
mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget);
mOverviewCallbacks = overviewCallbacks;
+ mTaskOverlayFactory = taskOverlayFactory;
}
@Override
@@ -233,14 +235,22 @@
handler.initWhenReady();
TraceHelper.beginSection("RecentsController");
- Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
- mHomeIntent,
+
+ AssistDataReceiver assistDataReceiver = !mTaskOverlayFactory.needAssist() ? null :
new AssistDataReceiver() {
@Override
public void onHandleAssistData(Bundle bundle) {
- mRecentsModel.preloadAssistData(mRunningTask.id, bundle);
+ if (mInteractionHandler == null) {
+ // Interaction is probably complete
+ mRecentsModel.preloadAssistData(mRunningTask.id, bundle);
+ } else if (handler == mInteractionHandler) {
+ handler.onAssistDataReceived(bundle);
+ }
}
- }, animationState, null, null);
+ };
+
+ Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+ mHomeIntent, assistDataReceiver, animationState, null, null);
if (Looper.myLooper() != Looper.getMainLooper()) {
startActivity.run();
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 41a4550..eff94fc 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -67,6 +67,7 @@
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
import com.android.systemui.shared.system.TransactionCompat;
import java.util.ArrayList;
@@ -350,11 +351,14 @@
clipHelper.updateTargetRect(targetRect);
clipHelper.prepareAnimation(false /* isOpening */);
+ SyncRtSurfaceTransactionApplier syncTransactionApplier =
+ new SyncRtSurfaceTransactionApplier(rootView);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(RECENTS_LAUNCH_DURATION);
valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
valueAnimator.addUpdateListener((v) ->
- clipHelper.applyTransform(targetSet, (float) v.getAnimatedValue()));
+ clipHelper.applyTransform(targetSet, (float) v.getAnimatedValue(),
+ syncTransactionApplier));
if (targetSet.isAnimatingHome()) {
// If we are animating home, fade in the opening targets
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index 7a79c6f..cbc7a67 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -98,7 +98,7 @@
}
int page = mRecentsView.getNextPage();
Runnable launchTaskRunnable = () -> {
- TaskView taskView = mRecentsView.getPageAt(page);
+ TaskView taskView = mRecentsView.getTaskViewAt(page);
if (taskView != null) {
mWaitingForTaskLaunch = true;
taskView.launchTask(true, (result) -> {
@@ -108,7 +108,7 @@
} else {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(Touch.DRAGDROP,
LauncherLogProto.Action.Direction.NONE, page,
- TaskUtils.getComponentKeyForTask(taskView.getTask().key));
+ TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key));
}
mWaitingForTaskLaunch = false;
}, taskView.getHandler());
@@ -222,7 +222,7 @@
private void goToPageWithHaptic(int pageToGoTo, int overrideDuration, boolean forceHaptic,
Interpolator interpolator) {
- pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
+ pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getTaskViewCount() - 1);
boolean snappingToPage = pageToGoTo != mRecentsView.getNextPage();
if (snappingToPage) {
int duration = overrideDuration > -1 ? overrideDuration
@@ -246,7 +246,7 @@
return;
}
if (mQuickScrubSection == QUICK_SCRUB_THRESHOLDS.length
- && currPage < mRecentsView.getPageCount() - 1) {
+ && currPage < mRecentsView.getTaskViewCount() - 1) {
goToPageWithHaptic(currPage + 1);
} else if (mQuickScrubSection == 0 && currPage > 0) {
goToPageWithHaptic(currPage - 1);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 3adb290..1d7c066 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.LauncherAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -52,7 +53,6 @@
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsRootView;
import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -70,7 +70,6 @@
private Handler mUiHandler = new Handler(Looper.getMainLooper());
private RecentsRootView mRecentsRootView;
private FallbackRecentsView mFallbackRecentsView;
- private RecentsViewContainer mOverviewPanelContainer;
private Configuration mOldConfig;
@@ -84,7 +83,6 @@
setContentView(R.layout.fallback_recents_activity);
mRecentsRootView = findViewById(R.id.drag_layer);
mFallbackRecentsView = findViewById(R.id.overview_panel);
- mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
mRecentsRootView.setup();
@@ -166,10 +164,6 @@
return (T) mFallbackRecentsView;
}
- public RecentsViewContainer getOverviewPanelContainer() {
- return mOverviewPanelContainer;
- }
-
@Override
public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
return null;
@@ -193,7 +187,8 @@
};
return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
runner, RECENTS_LAUNCH_DURATION,
- RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION));
+ RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
+ - STATUS_BAR_TRANSITION_PRE_DELAY));
}
/**
@@ -246,6 +241,12 @@
}
@Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ UiFactory.onEnterAnimationComplete(this);
+ }
+
+ @Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
UiFactory.onTrimMemory(this, level);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index 34d42ac..b0313fc 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -49,6 +49,9 @@
this.mController = controller;
this.targetSet = targetSet;
+ if (controller == null) {
+ return;
+ }
if (mInputConsumerEnabled) {
enableInputConsumer();
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 9c2c8b3..0b97f01 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -232,7 +232,6 @@
public void onStart() {
mRecentsTaskLoader.startLoader(mContext);
- mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
}
public void onTrimMemory(int level) {
@@ -256,6 +255,10 @@
}
}
+ public void resetAssistCache() {
+ mCachedAssistData.clear();
+ }
+
@WorkerThread
public void preloadAssistData(int taskId, Bundle data) {
mMainThreadExecutor.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 66969c6..9d3ac6a 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Matrix;
+import android.support.annotation.AnyThread;
import android.view.View;
import com.android.launcher3.R;
@@ -42,6 +43,11 @@
return sInstance;
}
+ @AnyThread
+ public boolean needAssist() {
+ return false;
+ }
+
public TaskOverlay createOverlay(View thumbnailView) {
return new TaskOverlay();
}
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index f82ff8c..e5a2b5e 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -30,7 +30,6 @@
import android.util.Log;
import android.view.View;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
@@ -152,8 +151,7 @@
}
};
- AbstractFloatingView.closeOpenViews(activity, true,
- AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+ dismissTaskMenuView(activity);
final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition();
if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
@@ -190,9 +188,14 @@
final Rect taskBounds = new Rect(position[0], position[1],
position[0] + width, position[1] + height);
+ // Take the thumbnail of the task without a scrim and apply it back after
+ float alpha = thumbnailView.getDimAlpha();
+ thumbnailView.setDimAlpha(0);
Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
Color.BLACK);
+ thumbnailView.setDimAlpha(alpha);
+
AppTransitionAnimationSpecsFuture future =
new AppTransitionAnimationSpecsFuture(mHandler) {
@Override
@@ -246,6 +249,7 @@
}
};
taskView.launchTask(true, resultCallback, mHandler);
+ dismissTaskMenuView(activity);
};
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 2b0c98f..f9b5e30 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -46,6 +46,7 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
import java.util.List;
@@ -74,8 +75,11 @@
applicationInfo.loadLabel(packageManager), user);
}
- public static ComponentKey getComponentKeyForTask(Task.TaskKey taskKey) {
- return new ComponentKey(taskKey.getComponent(), UserHandle.of(taskKey.userId));
+ public static ComponentKey getLaunchComponentKeyForTask(Task.TaskKey taskKey) {
+ final ComponentName cn = taskKey.sourceComponent != null
+ ? taskKey.sourceComponent
+ : taskKey.getComponent();
+ return new ComponentKey(cn, UserHandle.of(taskKey.userId));
}
@@ -100,8 +104,8 @@
ComponentName componentName = itemInfo.getTargetComponent();
int userId = itemInfo.user.getIdentifier();
if (componentName != null) {
- for (int i = 0; i < recentsView.getChildCount(); i++) {
- TaskView taskView = recentsView.getPageAt(i);
+ for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
+ TaskView taskView = recentsView.getTaskViewAt(i);
if (recentsView.isTaskViewVisible(taskView)) {
Task.TaskKey key = taskView.getTask().key;
if (componentName.equals(key.getComponent()) && userId == key.userId) {
@@ -144,6 +148,8 @@
*/
public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper) {
+ SyncRtSurfaceTransactionApplier syncTransactionApplier =
+ new SyncRtSurfaceTransactionApplier(v);
final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
@@ -155,18 +161,10 @@
final RemoteAnimationTargetSet mTargetSet;
final RectF mThumbnailRect;
- private Surface mSurface;
- private long mFrameNumber;
{
mTargetSet = new RemoteAnimationTargetSet(targets, MODE_OPENING);
- inOutHelper.setTaskTransformCallback((t, app) -> {
- t.setAlpha(app.leash, mTaskAlpha.value);
-
- if (!skipViewChanges) {
- t.deferTransactionUntil(app.leash, mSurface, mFrameNumber);
- }
- });
+ inOutHelper.setTaskAlphaCallback((t, alpha) -> mTaskAlpha.value);
inOutHelper.prepareAnimation(true /* isOpening */);
inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
@@ -179,15 +177,8 @@
@Override
public void onUpdate(float percent) {
- mSurface = getSurface(v);
- mFrameNumber = mSurface != null ? getNextFrameNumber(mSurface) : -1;
- if (mFrameNumber == -1) {
- // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
- Log.w(TAG, "Failed to animate, surface got destroyed.");
- return;
- }
-
- RectF taskBounds = inOutHelper.applyTransform(mTargetSet, 1 - percent);
+ RectF taskBounds = inOutHelper.applyTransform(mTargetSet, 1 - percent,
+ syncTransactionApplier);
if (!skipViewChanges) {
float scale = taskBounds.width() / mThumbnailRect.width();
v.setScaleX(scale);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index aecb66c..6c54262 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -49,6 +49,7 @@
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ChoreographerCompat;
import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
/**
@@ -170,6 +171,7 @@
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewInteractionState mOverviewInteractionState;
private OverviewCallbacks mOverviewCallbacks;
+ private TaskOverlayFactory mTaskOverlayFactory;
private Choreographer mMainThreadChoreographer;
private Choreographer mBackgroundThreadChoreographer;
@@ -186,6 +188,7 @@
mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
mOverviewInteractionState = OverviewInteractionState.getInstance(this);
mOverviewCallbacks = OverviewCallbacks.get(this);
+ mTaskOverlayFactory = TaskOverlayFactory.get(this);
sConnected = true;
@@ -238,7 +241,7 @@
mOverviewCommandHelper.overviewIntent,
mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
- tracker);
+ mTaskOverlayFactory, tracker);
}
}
@@ -406,6 +409,6 @@
sRemoteUiThread.start();
}
new Handler(sRemoteUiThread.getLooper()).post(() ->
- mBackgroundThreadChoreographer = Choreographer.getInstance());
+ mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance());
}
}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index b1663b1..1f0a057 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -16,13 +16,16 @@
package com.android.quickstep;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
+import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
+import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -35,6 +38,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
@@ -43,6 +47,7 @@
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.Log;
+import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowManager;
@@ -57,6 +62,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -79,10 +85,12 @@
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
import com.android.systemui.shared.system.WindowCallbacksCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.StringJoiner;
+import java.util.function.BiFunction;
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
@@ -103,19 +111,21 @@
private static final int STATE_SCALED_CONTROLLER_APP = 1 << 6;
private static final int STATE_HANDLER_INVALIDATED = 1 << 7;
- private static final int STATE_GESTURE_STARTED = 1 << 8;
- private static final int STATE_GESTURE_CANCELLED = 1 << 9;
- private static final int STATE_GESTURE_COMPLETED = 1 << 10;
+ private static final int STATE_GESTURE_STARTED_QUICKSTEP = 1 << 8;
+ private static final int STATE_GESTURE_STARTED_QUICKSCRUB = 1 << 9;
+ private static final int STATE_GESTURE_CANCELLED = 1 << 10;
+ private static final int STATE_GESTURE_COMPLETED = 1 << 11;
// States for quick switch/scrub
- private static final int STATE_CURRENT_TASK_FINISHED = 1 << 11;
- private static final int STATE_QUICK_SCRUB_START = 1 << 12;
- private static final int STATE_QUICK_SCRUB_END = 1 << 13;
+ private static final int STATE_CURRENT_TASK_FINISHED = 1 << 12;
+ private static final int STATE_QUICK_SCRUB_START = 1 << 13;
+ private static final int STATE_QUICK_SCRUB_END = 1 << 14;
- private static final int STATE_CAPTURE_SCREENSHOT = 1 << 14;
- private static final int STATE_SCREENSHOT_CAPTURED = 1 << 15;
+ private static final int STATE_CAPTURE_SCREENSHOT = 1 << 15;
+ private static final int STATE_SCREENSHOT_CAPTURED = 1 << 16;
- private static final int STATE_RESUME_LAST_TASK = 1 << 16;
+ private static final int STATE_RESUME_LAST_TASK = 1 << 17;
+ private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 18;
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
@@ -139,7 +149,8 @@
"STATE_SCALED_CONTROLLER_RECENTS",
"STATE_SCALED_CONTROLLER_APP",
"STATE_HANDLER_INVALIDATED",
- "STATE_GESTURE_STARTED",
+ "STATE_GESTURE_STARTED_QUICKSTEP",
+ "STATE_GESTURE_STARTED_QUICKSCRUB",
"STATE_GESTURE_CANCELLED",
"STATE_GESTURE_COMPLETED",
"STATE_CURRENT_TASK_FINISHED",
@@ -148,12 +159,14 @@
"STATE_CAPTURE_SCREENSHOT",
"STATE_SCREENSHOT_CAPTURED",
"STATE_RESUME_LAST_TASK",
+ "STATE_ASSIST_DATA_RECEIVED",
};
public static final long MAX_SWIPE_DURATION = 350;
public static final long MIN_SWIPE_DURATION = 80;
+ public static final long MIN_OVERSHOOT_DURATION = 120;
- private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
+ public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
private static final float SWIPE_DURATION_MULTIPLIER =
Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
@@ -192,6 +205,7 @@
private T mActivity;
private LayoutListener mLayoutListener;
private RecentsView mRecentsView;
+ private SyncRtSurfaceTransactionApplier mSyncTransactionApplier;
private QuickScrubController mQuickScrubController;
private AnimationFactory mAnimationFactory = (t, i) -> { };
@@ -199,6 +213,7 @@
private boolean mWasLauncherAlreadyVisible;
+ private boolean mPassedOverviewThreshold;
private boolean mGestureStarted;
private int mLogAction = Touch.SWIPE;
private float mCurrentQuickScrubProgress;
@@ -219,6 +234,8 @@
private float mLongSwipeDisplacement = 0;
private LongSwipeHelper mLongSwipeController;
+ private Bundle mAssistData;
+
WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
long touchTimeMs, ActivityControlHelper<T> controller) {
this.id = id;
@@ -245,12 +262,19 @@
}
};
- mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
+ mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED_QUICKSCRUB,
this::initializeLauncherAnimationController);
+ mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED_QUICKSTEP,
+ this::initializeLauncherAnimationController);
+
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED_QUICKSTEP,
this::notifyGestureStartedAsync);
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED_QUICKSCRUB,
+ this::notifyGestureStartedAsync);
+
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
@@ -273,11 +297,15 @@
this::finishCurrentTransitionToHome);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_ACTIVITY_MULTIPLIER_COMPLETE
- | STATE_SCALED_CONTROLLER_RECENTS
- | STATE_CURRENT_TASK_FINISHED
- | STATE_GESTURE_COMPLETED,
+ | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
+ | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
+ | STATE_GESTURE_STARTED_QUICKSTEP,
this::setupLauncherUiAfterSwipeUpAnimation);
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+ | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
+ | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
+ | STATE_GESTURE_STARTED_QUICKSTEP | STATE_ASSIST_DATA_RECEIVED,
+ this::preloadAssistData);
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
@@ -354,12 +382,13 @@
// Override the visibility of the activity until the gesture actually starts and we swipe
// up, or until we transition home and the home animation is composed
if (alreadyOnHome) {
- mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
} else {
- mActivity.addForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
}
mRecentsView = activity.getOverviewPanel();
+ mSyncTransactionApplier = new SyncRtSurfaceTransactionApplier(mRecentsView);
mQuickScrubController = mRecentsView.getQuickScrubController();
mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity);
@@ -454,6 +483,12 @@
if (LatencyTrackerCompat.isEnabled(mContext)) {
LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs));
}
+
+ // This method is only called when STATE_GESTURE_STARTED_QUICKSTEP/
+ // STATE_GESTURE_STARTED_QUICKSCRUB is set, so we can enable the high-res thumbnail loader
+ // here once we are sure that we will end up in an overview state
+ RecentsModel.getInstance(mContext).getRecentsTaskLoader()
+ .getHighResThumbnailLoader().setVisible(true);
}
public void updateInteractionType(@InteractionType int interactionType) {
@@ -471,7 +506,8 @@
setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
// Start the window animation without waiting for launcher.
- animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR);
+ animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR,
+ true /* goingToHome */);
}
private void shiftAnimationDestinationForQuickscrub() {
@@ -546,10 +582,13 @@
RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
if (controller != null) {
- mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, shift);
- // TODO: This logic is spartanic!
- boolean passedThreshold = shift > 0.12f;
+ mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, shift,
+ Looper.myLooper() == mMainThreadHandler.getLooper()
+ ? mSyncTransactionApplier
+ : null);
+
+ boolean passedThreshold = shift > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
if (mActivityControlHelper.shouldMinimizeSplitScreen()) {
mRecentsAnimationWrapper.setSplitScreenMinimizedForTransaction(passedThreshold);
@@ -560,7 +599,17 @@
}
private void updateFinalShiftUi() {
- if (mLauncherTransitionController == null) {
+ final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
+ if (passed != mPassedOverviewThreshold) {
+ mPassedOverviewThreshold = passed;
+ if (mInteractionType == INTERACTION_NORMAL && mRecentsView != null) {
+ mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
+ }
+
+ if (mLauncherTransitionController == null || mLauncherTransitionController
+ .getAnimationPlayer().isStarted()) {
return;
}
mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
@@ -582,7 +631,15 @@
new Point(minimizedHomeBounds.width(), minimizedHomeBounds.height()));
dp.updateInsets(homeContentInsets);
} else {
- overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
+ if (mActivity != null) {
+ int loc[] = new int[2];
+ View rootView = mActivity.getRootView();
+ rootView.getLocationOnScreen(loc);
+ overviewStackBounds = new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
+ loc[1] + rootView.getHeight());
+ } else {
+ overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
+ }
// If we are not in multi-window mode, home insets should be same as system insets.
Rect insets = new Rect();
WindowManagerWrapper.getInstance().getStableInsets(insets);
@@ -599,6 +656,8 @@
mRecentsAnimationWrapper.setController(controller, targets);
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+
+ mPassedOverviewThreshold = false;
}
public void onRecentsAnimationCanceled() {
@@ -609,7 +668,8 @@
public void onGestureStarted() {
notifyGestureStartedAsync();
- setStateOnUiThread(STATE_GESTURE_STARTED);
+ setStateOnUiThread(mInteractionType == INTERACTION_NORMAL
+ ? STATE_GESTURE_STARTED_QUICKSTEP : STATE_GESTURE_STARTED_QUICKSCRUB);
mGestureStarted = true;
mRecentsAnimationWrapper.hideCurrentInputMethod();
mRecentsAnimationWrapper.enableInputConsumer();
@@ -625,7 +685,7 @@
if (curActivity != null) {
// Once the gesture starts, we can no longer transition home through the button, so
// reset the force override of the activity visibility
- mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
}
}
@@ -646,45 +706,65 @@
}
private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
+ float velocityPxPerMs = endVelocity / 1000;
long duration = MAX_SWIPE_DURATION;
- final float endShift;
+ float currentShift = mCurrentShift.value;
+ final boolean goingToHome;
+ float endShift;
final float startShift;
+ Interpolator interpolator = DEACCEL;
if (!isFling) {
- endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted ? 1 : 0;
- long expectedDuration = Math.abs(Math.round((endShift - mCurrentShift.value)
+ goingToHome = currentShift >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted;
+ endShift = goingToHome ? 1 : 0;
+ long expectedDuration = Math.abs(Math.round((endShift - currentShift)
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
- startShift = mCurrentShift.value;
+ startShift = currentShift;
+ interpolator = goingToHome ? OVERSHOOT_1_2 : DEACCEL;
} else {
- endShift = endVelocity < 0 ? 1 : 0;
+ goingToHome = endVelocity < 0;
+ endShift = goingToHome ? 1 : 0;
+ startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
+ * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
- float distanceToTravel = (endShift - mCurrentShift.value) * mTransitionDragLength;
+ if (goingToHome) {
+ Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
+ startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
+ endShift = overshoot.end;
+ interpolator = overshoot.interpolator;
+ duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
+ MAX_SWIPE_DURATION);
+ } else {
+ float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
- // we want the page's snap velocity to approximately match the velocity at
- // which the user flings, so we scale the duration by a value near to the
- // derivative of the scroll interpolator at zero, ie. 2.
- long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
- duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+ // we want the page's snap velocity to approximately match the velocity at
+ // which the user flings, so we scale the duration by a value near to the
+ // derivative of the scroll interpolator at zero, ie. 2.
+ long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
+ duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+ }
}
- startShift = Utilities.boundToRange(mCurrentShift.value - endVelocity * SINGLE_FRAME_MS
- / (mTransitionDragLength * 1000), 0, 1);
}
-
- animateToProgress(startShift, endShift, duration, DEACCEL);
+ animateToProgress(startShift, endShift, duration, interpolator, goingToHome);
}
private void doLogGesture(boolean toLauncher) {
+ DeviceProfile dp = mDp;
+ if (dp == null) {
+ // We probably never received an animation controller, skip logging.
+ return;
+ }
final int direction;
- if (mDp.isVerticalBarLayout()) {
- direction = (mDp.isSeascape() ^ toLauncher) ? Direction.LEFT : Direction.RIGHT;
+ if (dp.isVerticalBarLayout()) {
+ direction = (dp.isSeascape() ^ toLauncher) ? Direction.LEFT : Direction.RIGHT;
} else {
direction = toLauncher ? Direction.UP : Direction.DOWN;
}
int dstContainerType = toLauncher ? ContainerType.TASKSWITCHER : ContainerType.APP;
- UserEventDispatcher.newInstance(mContext, mDp).logStateChangeAction(
+ UserEventDispatcher.newInstance(mContext, dp).logStateChangeAction(
mLogAction, direction,
ContainerType.NAVBAR, ContainerType.APP,
dstContainerType,
@@ -693,8 +773,14 @@
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
private void animateToProgress(float start, float end, long duration,
- Interpolator interpolator) {
- mIsGoingToHome = Float.compare(end, 1) == 0;
+ Interpolator interpolator, boolean goingToHome) {
+ mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
+ interpolator, goingToHome));
+ }
+
+ private void animateToProgressInternal(float start, float end, long duration,
+ Interpolator interpolator, boolean goingToHome) {
+ mIsGoingToHome = goingToHome;
ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration);
anim.setInterpolator(interpolator);
anim.addListener(new AnimationSuccessListener() {
@@ -705,7 +791,26 @@
: STATE_SCALED_CONTROLLER_APP);
}
});
- mRecentsAnimationWrapper.runOnInit(anim::start);
+ anim.start();
+ long startMillis = SystemClock.uptimeMillis();
+ executeOnUiThread(() -> {
+ // Animate the launcher components at the same time as the window, always on UI thread.
+ if (mLauncherTransitionController != null && !mWasLauncherAlreadyVisible
+ && start != end && duration > 0) {
+ // Adjust start progress and duration in case we are on a different thread.
+ long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
+ elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration);
+ float elapsedProgress = (float) elapsedMillis / duration;
+ float adjustedStart = Utilities.mapRange(elapsedProgress, start, end);
+ long adjustedDuration = duration - elapsedMillis;
+ // We want to use the same interpolator as the window, but need to adjust it to
+ // interpolate over the remaining progress (end - start).
+ mLauncherTransitionController.dispatchSetInterpolator(Interpolators.mapToProgress(
+ interpolator, adjustedStart, end));
+ mLauncherTransitionController.getAnimationPlayer().setDuration(adjustedDuration);
+ mLauncherTransitionController.getAnimationPlayer().start();
+ }
+ });
}
@UiThread
@@ -757,6 +862,9 @@
private void resetStateForAnimationCancel() {
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
+
+ // Leave the pending invisible flag, as it may be used by wallpaper open animation.
+ mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
}
public void layoutListenerClosed() {
@@ -780,8 +888,21 @@
// new thumbnail
finishTransitionPosted = new WindowCallbacksCompat(taskView) {
+ // The number of frames to defer until we actually finish the animation
+ private int mDeferFrameCount = 2;
+
@Override
public void onPostDraw(Canvas canvas) {
+ if (mDeferFrameCount > 0) {
+ mDeferFrameCount--;
+ // Workaround, detach and reattach to invalidate the root node for
+ // another draw
+ detach();
+ attach();
+ taskView.invalidate();
+ return;
+ }
+
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
detach();
}
@@ -840,6 +961,9 @@
return;
}
mQuickScrubController.onFinishedTransitionToQuickScrub();
+
+ mRecentsView.setRunningTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
+ RecentsModel.getInstance(mContext).onOverviewShown(false, TAG);
}
public void onQuickScrubProgress(float progress) {
@@ -903,6 +1027,7 @@
if (mLongSwipeController != null) {
mLongSwipeController.destroy();
+ setTargetAlphaProvider((t, a1) -> a1);
// Rebuild animations
buildAnimationController();
@@ -944,6 +1069,7 @@
mLongSwipeController = mActivityControlHelper.getLongSwipeController(
mActivity, mRecentsAnimationWrapper.targetSet);
onLongSwipeDisplacementUpdated();
+ setTargetAlphaProvider(mLongSwipeController::getTargetAlpha);
}
private void onLongSwipeGestureFinishUi(float velocity, boolean isFling) {
@@ -958,4 +1084,19 @@
() -> setStateOnUiThread(STATE_HANDLER_INVALIDATED));
}
+
+ private void setTargetAlphaProvider(
+ BiFunction<RemoteAnimationTargetCompat, Float, Float> provider) {
+ mClipAnimationHelper.setTaskAlphaCallback(provider);
+ updateFinalShift();
+ }
+
+ public void onAssistDataReceived(Bundle assistData) {
+ mAssistData = assistData;
+ setStateOnUiThread(STATE_ASSIST_DATA_RECEIVED);
+ }
+
+ private void preloadAssistData() {
+ RecentsModel.getInstance(mContext).preloadAssistData(mRunningTaskId, mAssistData);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 9e2de33..261f45d 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -39,7 +39,7 @@
}
@Override
- protected void onAllTasksRemoved() {
+ protected void startHome() {
mActivity.startHome();
}
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index a654482..df70a8a 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -30,6 +30,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.support.annotation.Nullable;
+import android.view.Surface;
import android.view.animation.Interpolator;
import com.android.launcher3.BaseDraggingActivity;
@@ -44,10 +45,13 @@
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams;
import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
/**
* Utility class to handle window clip animation
@@ -90,8 +94,8 @@
// Wether or not applyTransform has been called yet since prepareAnimation()
private boolean mIsFirstFrame = true;
- private BiConsumer<TransactionCompat, RemoteAnimationTargetCompat> mTaskTransformCallback =
- (t, a) -> { };
+ private BiFunction<RemoteAnimationTargetCompat, Float, Float> mTaskAlphaCallback =
+ (t, a1) -> a1;
private void updateSourceStack(RemoteAnimationTargetCompat target) {
mSourceInsets.set(target.contentInsets);
@@ -134,11 +138,11 @@
}
public void prepareAnimation(boolean isOpening) {
- mIsFirstFrame = true;
mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
}
- public RectF applyTransform(RemoteAnimationTargetSet targetSet, float progress) {
+ public RectF applyTransform(RemoteAnimationTargetSet targetSet, float progress,
+ @Nullable SyncRtSurfaceTransactionApplier syncTransactionApplier) {
RectF currentRect;
mTmpRectF.set(mTargetRect);
Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale);
@@ -159,35 +163,51 @@
mClipRect.bottom = (int)
(mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress));
- TransactionCompat transaction = new TransactionCompat();
- if (mIsFirstFrame) {
- RemoteAnimationProvider.prepareTargetsForFirstFrame(targetSet.unfilteredApps,
- transaction, mBoostModeTargetLayers);
- mIsFirstFrame = false;
- }
- for (RemoteAnimationTargetCompat app : targetSet.apps) {
- if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
- mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
- mTmpMatrix.postTranslate(app.position.x, app.position.y);
- transaction.setMatrix(app.leash, mTmpMatrix)
- .setWindowCrop(app.leash, mClipRect);
+ SurfaceParams[] params = new SurfaceParams[targetSet.unfilteredApps.length];
+ for (int i = 0; i < targetSet.unfilteredApps.length; i++) {
+ RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i];
+ mTmpMatrix.setTranslate(app.position.x, app.position.y);
+ Rect crop = app.sourceContainerBounds;
+ float alpha = 1f;
+ if (app.mode == targetSet.targetMode) {
+ if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
+ mTmpMatrix.postTranslate(app.position.x, app.position.y);
+ crop = mClipRect;
+ }
+
+ if (app.isNotInRecents
+ || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ alpha = 1 - progress;
+ }
+
+ alpha = mTaskAlphaCallback.apply(app, alpha);
}
- if (app.isNotInRecents
- || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
- transaction.setAlpha(app.leash, 1 - progress);
- }
-
- mTaskTransformCallback.accept(transaction, app);
+ params[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop,
+ RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers));
}
- transaction.setEarlyWakeup();
- transaction.apply();
+ applyParams(syncTransactionApplier, params);
return currentRect;
}
- public void setTaskTransformCallback
- (BiConsumer<TransactionCompat, RemoteAnimationTargetCompat> callback) {
- mTaskTransformCallback = callback;
+ private void applyParams(@Nullable SyncRtSurfaceTransactionApplier syncTransactionApplier,
+ SurfaceParams[] params) {
+ if (syncTransactionApplier != null) {
+ syncTransactionApplier.scheduleApply(params);
+ } else {
+ TransactionCompat t = new TransactionCompat();
+ for (SurfaceParams param : params) {
+ SyncRtSurfaceTransactionApplier.applyParams(t, param);
+ }
+ t.setEarlyWakeup();
+ t.apply();
+ }
+ }
+
+ public void setTaskAlphaCallback(
+ BiFunction<RemoteAnimationTargetCompat, Float, Float> callback) {
+ mTaskAlphaCallback = callback;
}
public void offsetTarget(float scale, float offsetX, float offsetY, Interpolator interpolator) {
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index bbf223d..a7e6d74 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -28,6 +28,8 @@
@FunctionalInterface
public interface RemoteAnimationProvider {
+ static final int Z_BOOST_BASE = 800570000;
+
AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
default ActivityOptions toActivityOptions(Handler handler, long duration) {
@@ -54,11 +56,14 @@
static void prepareTargetsForFirstFrame(RemoteAnimationTargetCompat[] targets,
TransactionCompat t, int boostModeTargets) {
for (RemoteAnimationTargetCompat target : targets) {
- int layer = target.mode == boostModeTargets
- ? Integer.MAX_VALUE
- : target.prefixOrderIndex;
- t.setLayer(target.leash, layer);
+ t.setLayer(target.leash, getLayer(target, boostModeTargets));
t.show(target.leash);
}
}
+
+ static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) {
+ return target.mode == boostModeTarget
+ ? Z_BOOST_BASE + target.prefixOrderIndex
+ : target.prefixOrderIndex;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
index 04b8be5..c372485 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
@@ -26,6 +26,7 @@
public final RemoteAnimationTargetCompat[] unfilteredApps;
public final RemoteAnimationTargetCompat[] apps;
+ public final int targetMode;
public RemoteAnimationTargetSet(RemoteAnimationTargetCompat[] apps, int targetMode) {
ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
@@ -39,6 +40,7 @@
this.unfilteredApps = apps;
this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
+ this.targetMode = targetMode;
}
public RemoteAnimationTargetCompat findTask(int taskId) {
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 0025df1..fbecd84 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -17,34 +17,63 @@
package com.android.quickstep.views;
import android.content.Context;
-import android.graphics.Rect;
-import android.support.annotation.Nullable;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
-public class ClearAllButton extends Button {
- RecentsView mRecentsView;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.views.RecentsView.PageCallbacks;
+import com.android.quickstep.views.RecentsView.ScrollState;
- public ClearAllButton(Context context, @Nullable AttributeSet attrs) {
+public class ClearAllButton extends Button implements PageCallbacks {
+
+ private float mScrollAlpha = 1;
+ private float mContentAlpha = 1;
+
+ private final boolean mIsRtl;
+
+ private int mScrollOffset;
+
+ public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
- }
-
- public void setRecentsView(RecentsView recentsView) {
- mRecentsView = recentsView;
+ mIsRtl = Utilities.isRtl(context.getResources());
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setParent(mRecentsView); // Pretend we are a part of the task carousel.
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ RecentsView parent = (RecentsView) getParent();
+ mScrollOffset = mIsRtl ? parent.getPaddingRight() / 2 : - parent.getPaddingLeft() / 2;
}
@Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- if (focused) {
- mRecentsView.revealClearAllButton();
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ public void setContentAlpha(float alpha) {
+ if (mContentAlpha != alpha) {
+ mContentAlpha = alpha;
+ updateAlpha();
}
}
+
+ @Override
+ public void onPageScroll(ScrollState scrollState) {
+ float width = getWidth();
+ if (width == 0) {
+ return;
+ }
+
+ float shift = Math.min(scrollState.scrollFromEdge, width);
+ setTranslationX(mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift));
+ mScrollAlpha = 1 - shift / width;
+ updateAlpha();
+ }
+
+ private void updateAlpha() {
+ final float alpha = mScrollAlpha * mContentAlpha;
+ setAlpha(alpha);
+ setClickable(alpha == 1);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5aca4f3..7c5828b 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -79,7 +79,7 @@
}
@Override
- protected void onAllTasksRemoved() {
+ protected void startHome() {
mActivity.getStateManager().goToState(NORMAL);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d550edc..e18708b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -16,13 +16,14 @@
package com.android.quickstep.views;
-import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
+import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -39,7 +40,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.support.annotation.Nullable;
@@ -48,11 +48,14 @@
import android.text.TextPaint;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.util.SparseBooleanArray;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -98,11 +101,24 @@
private static final String TAG = RecentsView.class.getSimpleName();
+ public static final FloatProperty<RecentsView> CONTENT_ALPHA =
+ new FloatProperty<RecentsView>("contentAlpha") {
+ @Override
+ public void setValue(RecentsView view, float v) {
+ view.setContentAlpha(v);
+ }
+
+ @Override
+ public Float get(RecentsView view) {
+ return view.getContentAlpha();
+ }
+ };
+
private final Rect mTempRect = new Rect();
private static final int DISMISS_TASK_DURATION = 300;
// The threshold at which we update the SystemUI flags when animating from the task into the app
- private static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.6f;
+ public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f;
private static final float[] sTempFloatArray = new float[3];
@@ -111,13 +127,14 @@
private final float mFastFlingVelocity;
private final RecentsModel mModel;
private final int mTaskTopMargin;
+ private final ClearAllButton mClearAllButton;
+ private final Rect mClearAllButtonDeadZoneRect = new Rect();
+ private final Rect mTaskViewDeadZoneRect = new Rect();
private final ScrollState mScrollState = new ScrollState();
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
- private boolean mIsClearAllButtonFullyRevealed;
-
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -199,7 +216,7 @@
public void onPinnedStackAnimationStarted() {
// Needed for activities that auto-enter PiP, which will not trigger a remote
// animation to be created
- mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
}
};
@@ -216,6 +233,10 @@
private boolean mHandleTaskStackChanges;
private Runnable mNextPageSwitchRunnable;
private boolean mSwipeDownShouldLaunchApp;
+ private boolean mTouchDownToStartHome;
+ private final int mTouchSlop;
+ private int mDownX;
+ private int mDownY;
private PendingAnimation mPendingAnimation;
@@ -225,8 +246,6 @@
// Keeps track of task views whose visual state should not be reset
private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
- private View mClearAllButton;
-
// Variables for empty state
private final Drawable mEmptyIcon;
private final CharSequence mEmptyMessage;
@@ -248,7 +267,6 @@
super(context, attrs, defStyleAttr);
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
enableFreeScroll(true);
- setClipToOutline(true);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
@@ -256,10 +274,15 @@
mQuickScrubController = new QuickScrubController(mActivity, this);
mModel = RecentsModel.getInstance(context);
+ mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
+ .inflate(R.layout.overview_clear_all_button, this, false);
+ mClearAllButton.setOnClickListener(this::dismissAllTasks);
+
mIsRtl = !Utilities.isRtl(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mTaskTopMargin = getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
mEmptyIcon.setCallback(this);
@@ -272,7 +295,6 @@
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
updateEmptyMessage();
- setFocusable(false);
}
public boolean isRtl() {
@@ -314,14 +336,15 @@
super.onViewRemoved(child);
// Clear the task data for the removed child if it was visible
- Task task = ((TaskView) child).getTask();
- if (mHasVisibleTaskData.get(task.key.id)) {
- mHasVisibleTaskData.delete(task.key.id);
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- loader.unloadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ if (child != mClearAllButton) {
+ Task task = ((TaskView) child).getTask();
+ if (mHasVisibleTaskData.get(task.key.id)) {
+ mHasVisibleTaskData.delete(task.key.id);
+ RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+ loader.unloadTaskData(task);
+ loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ }
}
- onChildViewsChanged();
}
public boolean isTaskViewVisible(TaskView tv) {
@@ -330,7 +353,7 @@
}
public TaskView getTaskView(int taskId) {
- for (int i = 0; i < getChildCount(); i++) {
+ for (int i = 0; i < getTaskViewCount(); i++) {
TaskView tv = (TaskView) getChildAt(i);
if (tv.getTask().key.id == taskId) {
return tv;
@@ -360,75 +383,50 @@
}
}
- private int getScrollEnd() {
- return mIsRtl ? 0 : mMaxScrollX;
- }
-
- private float calculateClearAllButtonAlpha() {
- final int childCount = getChildCount();
- if (mShowEmptyMessage || childCount == 0 || mPageScrolls == null
- || childCount != mPageScrolls.length) {
- return 0;
- }
-
- final int scrollEnd = getScrollEnd();
- final int oldestChildScroll = getScrollForPage(childCount - 1);
-
- final int clearAllButtonMotionRange = scrollEnd - oldestChildScroll;
- if (clearAllButtonMotionRange == 0) return 0;
-
- final float alphaUnbound = ((float) (getScrollX() - oldestChildScroll)) /
- clearAllButtonMotionRange;
- if (alphaUnbound > 1) return 0;
-
- return Math.max(alphaUnbound, 0);
- }
-
- private void updateClearAllButtonAlpha() {
- if (mClearAllButton != null) {
- final float alpha = calculateClearAllButtonAlpha();
- final boolean revealed = alpha == 1;
- if (mIsClearAllButtonFullyRevealed != revealed) {
- mIsClearAllButtonFullyRevealed = revealed;
- mClearAllButton.setImportantForAccessibility(revealed ?
- IMPORTANT_FOR_ACCESSIBILITY_YES :
- IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
- mClearAllButton.setAlpha(alpha * mContentAlpha);
- }
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- updateClearAllButtonAlpha();
- }
-
- @Override
- protected void restoreScrollOnLayout() {
- if (mIsClearAllButtonFullyRevealed) {
- scrollAndForceFinish(getScrollEnd());
- } else {
- super.restoreScrollOnLayout();
- }
- }
-
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN && mTouchState == TOUCH_STATE_REST
- && mScroller.isFinished() && mIsClearAllButtonFullyRevealed) {
- mClearAllButton.getHitRect(mTempRect);
- mTempRect.offset(-getLeft(), -getTop());
- if (mTempRect.contains((int) ev.getX(), (int) ev.getY())) {
- // If nothing is in motion, let the Clear All button process the event.
- return false;
- }
+ super.onTouchEvent(ev);
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_UP:
+ if (mShowEmptyMessage) {
+ onAllTasksRemoved();
+ }
+ if (mTouchDownToStartHome) {
+ startHome();
+ }
+ mTouchDownToStartHome = false;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ mTouchDownToStartHome = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Passing the touch slop will not allow dismiss to home
+ if (mTouchDownToStartHome && Math.hypot(mDownX - x, mDownY - y) > mTouchSlop) {
+ mTouchDownToStartHome = false;
+ }
+ break;
+ case MotionEvent.ACTION_DOWN:
+ // Touch down anywhere but the deadzone around the visible clear all button and
+ // between the task views will start home on touch up
+ if (mTouchState == TOUCH_STATE_REST) {
+ updateDeadZoneRects();
+ final boolean clearAllButtonDeadZoneConsumed = mClearAllButton.getAlpha() == 1
+ && mClearAllButtonDeadZoneRect.contains(x, y);
+ if (!clearAllButtonDeadZoneConsumed
+ && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
+ mTouchDownToStartHome = true;
+ }
+ }
+ mDownX = x;
+ mDownY = y;
+ break;
}
- if (ev.getAction() == MotionEvent.ACTION_UP && mShowEmptyMessage) {
- onAllTasksRemoved();
- }
- return super.onTouchEvent(ev);
+
+ // Do not let touch escape to siblings below this view.
+ return true;
}
private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
@@ -450,22 +448,30 @@
final LayoutInflater inflater = LayoutInflater.from(getContext());
final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
- final int requiredChildCount = tasks.size();
- for (int i = getChildCount(); i < requiredChildCount; i++) {
- final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
- addView(taskView);
- }
- while (getChildCount() > requiredChildCount) {
- final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
- removeView(taskView);
+ final int requiredTaskCount = tasks.size();
+ if (getTaskViewCount() != requiredTaskCount) {
+ if (oldChildCount > 0) {
+ removeView(mClearAllButton);
+ }
+ for (int i = getChildCount(); i < requiredTaskCount; i++) {
+ final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
+ addView(taskView);
+ }
+ while (getChildCount() > requiredTaskCount) {
+ final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
+ removeView(taskView);
+ }
+ if (requiredTaskCount > 0) {
+ addView(mClearAllButton);
+ }
}
// Unload existing visible task data
unloadVisibleTaskData();
// Rebind and reset all task views
- for (int i = requiredChildCount - 1; i >= 0; i--) {
- final int pageIndex = requiredChildCount - i - 1;
+ for (int i = requiredTaskCount - 1; i >= 0; i--) {
+ final int pageIndex = requiredTaskCount - i - 1;
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
taskView.bind(task);
@@ -478,10 +484,16 @@
onTaskStackUpdated();
}
+ public int getTaskViewCount() {
+ // Account for the clear all button.
+ int childCount = getChildCount();
+ return childCount == 0 ? 0 : childCount - 1;
+ }
+
protected void onTaskStackUpdated() { }
public void resetTaskVisuals() {
- for (int i = getChildCount() - 1; i >= 0; i--) {
+ for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView taskView = (TaskView) getChildAt(i);
if (!mIgnoreResetTaskViews.contains(taskView)) {
taskView.resetVisualProperties();
@@ -555,10 +567,12 @@
if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
return;
}
+ int scrollX = getScrollX();
final int halfPageWidth = getNormalChildWidth() / 2;
- final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
+ final int screenCenter = mInsets.left + getPaddingLeft() + scrollX + halfPageWidth;
final int halfScreenWidth = getMeasuredWidth() / 2;
final int pageSpacing = mPageSpacing;
+ mScrollState.scrollFromEdge = mIsRtl ? scrollX : (mMaxScrollX - scrollX);
final int pageCount = getPageCount();
for (int i = 0; i < pageCount; i++) {
@@ -584,9 +598,9 @@
RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
int centerPageIndex = getPageNearestToCenterOfScreen();
+ int numChildren = getTaskViewCount();
int lower = Math.max(0, centerPageIndex - 2);
- int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
- int numChildren = getChildCount();
+ int upper = Math.min(centerPageIndex + 2, numChildren - 1);
// Update the task data for the in/visible children
for (int i = 0; i < numChildren; i++) {
@@ -629,7 +643,11 @@
mHasVisibleTaskData.clear();
}
- protected abstract void onAllTasksRemoved();
+ protected void onAllTasksRemoved() {
+ startHome();
+ }
+
+ protected abstract void startHome();
public void reset() {
mRunningTaskId = -1;
@@ -664,12 +682,14 @@
final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
.inflate(R.layout.task, this, false);
addView(taskView);
+ 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 = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(), 0, 0), null,
- null, "", "", 0, 0, false, true, false, false,
- new ActivityManager.TaskDescription(), 0, new ComponentName("", ""), false);
+ mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
+ new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
+ false, true, false, false, new ActivityManager.TaskDescription(), 0,
+ new ComponentName("", ""), false);
taskView.bind(mTmpRunningTask);
}
setCurrentTask(runningTaskId);
@@ -709,14 +729,14 @@
TaskView runningTaskView = getTaskView(mRunningTaskId);
if (runningTaskView == null) {
// Launch the first task
- if (getChildCount() > 0) {
+ if (getTaskViewCount() > 0) {
((TaskView) getChildAt(0)).launchTask(true /* animate */);
}
} else {
// Get the next launch task
int runningTaskIndex = indexOfChild(runningTaskView);
- int nextTaskIndex = Math.max(0, Math.min(getChildCount() - 1, runningTaskIndex + 1));
- if (nextTaskIndex < getChildCount()) {
+ int nextTaskIndex = Math.max(0, Math.min(getTaskViewCount() - 1, runningTaskIndex + 1));
+ if (nextTaskIndex < getTaskViewCount()) {
((TaskView) getChildAt(nextTaskIndex)).launchTask(true /* animate */);
}
}
@@ -759,7 +779,7 @@
/**
* Updates the page UI based on scroll params.
*/
- default void onPageScroll(ScrollState scrollState) {};
+ default void onPageScroll(ScrollState scrollState) {}
}
public static class ScrollState {
@@ -769,6 +789,11 @@
* of the screen and 1 is the edge of the screen.
*/
public float linearInterpolation;
+
+ /**
+ * The amount by which all the content is scrolled relative to the end of the list.
+ */
+ public float scrollFromEdge;
}
public void addIgnoreResetTask(TaskView taskView) {
@@ -792,7 +817,7 @@
if (shouldLog) {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
onEndListener.logAction, Direction.UP, index,
- TaskUtils.getComponentKeyForTask(task.key));
+ TaskUtils.getLaunchComponentKeyForTask(task.key));
}
}
}
@@ -805,7 +830,7 @@
AnimatorSet anim = new AnimatorSet();
PendingAnimation pendingAnimation = new PendingAnimation(anim);
- int count = getChildCount();
+ int count = getPageCount();
if (count == 0) {
return pendingAnimation;
}
@@ -816,12 +841,10 @@
int[] newScroll = new int[count];
getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
+ int taskCount = getTaskViewCount();
int scrollDiffPerPage = 0;
- int leftmostPage = mIsRtl ? count -1 : 0;
- int rightmostPage = mIsRtl ? 0 : count - 1;
if (count > 1) {
- int secondRightmostPage = mIsRtl ? 1 : count - 2;
- scrollDiffPerPage = oldScroll[rightmostPage] - oldScroll[secondRightmostPage];
+ scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
}
int draggedIndex = indexOfChild(taskView);
@@ -841,7 +864,7 @@
// - Dragging an adjacent page on the left side (right side for RTL)
int offset = mIsRtl ? scrollDiffPerPage : 0;
if (mCurrentPage == draggedIndex) {
- int lastPage = mIsRtl ? leftmostPage : rightmostPage;
+ int lastPage = taskCount - 1;
if (mCurrentPage == lastPage) {
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
@@ -879,13 +902,15 @@
removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
}
int pageToSnapTo = mCurrentPage;
- if (draggedIndex < pageToSnapTo) {
+ if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount() - 1)) {
pageToSnapTo -= 1;
}
removeView(taskView);
- if (getChildCount() == 0) {
+
+ if (getTaskViewCount() == 0) {
+ removeView(mClearAllButton);
onAllTasksRemoved();
- } else if (!mIsClearAllButtonFullyRevealed) {
+ } else {
snapToPageImmediately(pageToSnapTo);
}
}
@@ -902,7 +927,7 @@
AnimatorSet anim = new AnimatorSet();
PendingAnimation pendingAnimation = new PendingAnimation(anim);
- int count = getChildCount();
+ int count = getTaskViewCount();
for (int i = 0; i < count; i++) {
addDismissedTaskAnimations(getChildAt(i), anim, duration);
}
@@ -910,11 +935,11 @@
mPendingAnimation = pendingAnimation;
mPendingAnimation.addEndListener((onEndListener) -> {
if (onEndListener.isSuccess) {
- while (getChildCount() != 0) {
- TaskView taskView = getPageAt(getChildCount() - 1);
- removeTask(taskView.getTask(), -1, onEndListener, false);
- removeView(taskView);
+ int taskViewCount = getTaskViewCount();
+ for (int i = 0; i < taskViewCount; i++) {
+ removeTask(getTaskViewAt(i).getTask(), -1, onEndListener, false);
}
+ removeAllViews();
onAllTasksRemoved();
}
mPendingAnimation = null;
@@ -928,15 +953,16 @@
set.play(anim);
}
- private boolean snapToPageRelative(int delta, boolean cycle) {
- if (getPageCount() == 0) {
+ private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) {
+ if (pageCount == 0) {
return false;
}
final int newPageUnbound = getNextPage() + delta;
- if (!cycle && (newPageUnbound < 0 || newPageUnbound >= getChildCount())) {
+ if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) {
return false;
}
- snapToPage((newPageUnbound + getPageCount()) % getPageCount());
+ snapToPage((newPageUnbound + pageCount) % pageCount);
+ getChildAt(getNextPage()).requestFocus();
return true;
}
@@ -954,31 +980,37 @@
DISMISS_TASK_DURATION));
}
- public void dismissAllTasks() {
+ @SuppressWarnings("unused")
+ private void dismissAllTasks(View view) {
runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
}
+ private void dismissCurrentTask() {
+ TaskView taskView = getTaskView(getNextPage());
+ if (taskView != null) {
+ dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
+ }
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_TAB:
- return snapToPageRelative(event.isShiftPressed() ? -1 : 1,
+ return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1,
event.isAltPressed() /* cycle */);
case KeyEvent.KEYCODE_DPAD_RIGHT:
- return snapToPageRelative(mIsRtl ? -1 : 1, false /* cycle */);
+ return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */);
case KeyEvent.KEYCODE_DPAD_LEFT:
- return snapToPageRelative(mIsRtl ? 1 : -1, false /* cycle */);
+ return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */);
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_FORWARD_DEL:
- dismissTask((TaskView) getChildAt(getNextPage()), true /*animateTaskView*/,
- true /*removeTask*/);
+ dismissCurrentTask();
return true;
case KeyEvent.KEYCODE_NUMPAD_DOT:
if (event.isAltPressed()) {
// Numpad DEL pressed while holding Alt.
- dismissTask((TaskView) getChildAt(getNextPage()), true /*animateTaskView*/,
- true /*removeTask*/);
+ dismissCurrentTask();
return true;
}
}
@@ -1009,19 +1041,24 @@
}
public void setContentAlpha(float alpha) {
+ if (alpha == mContentAlpha) {
+ return;
+ }
alpha = Utilities.boundToRange(alpha, 0, 1);
mContentAlpha = alpha;
- for (int i = getChildCount() - 1; i >= 0; i--) {
- TaskView child = getPageAt(i);
+ for (int i = getTaskViewCount() - 1; i >= 0; i--) {
+ TaskView child = getTaskViewAt(i);
if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
getChildAt(i).setAlpha(alpha);
}
}
+ mClearAllButton.setContentAlpha(mContentAlpha);
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
- updateClearAllButtonAlpha();
+
+ setVisibility(alpha > 0 ? VISIBLE : GONE);
}
private float[] getAdjacentScaleAndTranslation(TaskView currTask,
@@ -1037,12 +1074,11 @@
public void onViewAdded(View child) {
super.onViewAdded(child);
child.setAlpha(mContentAlpha);
- onChildViewsChanged();
}
- @Override
- public TaskView getPageAt(int index) {
- return (TaskView) getChildAt(index);
+ public TaskView getTaskViewAt(int index) {
+ View child = getChildAt(index);
+ return child == mClearAllButton ? null : (TaskView) child;
}
public void updateEmptyMessage() {
@@ -1070,13 +1106,33 @@
+ (getWidth() - mInsets.right - getPaddingRight())) / 2);
}
+ private void updateDeadZoneRects() {
+ // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
+ mClearAllButtonDeadZoneRect.setEmpty();
+ if (mClearAllButton.getWidth() > 0) {
+ int verticalMargin = getResources()
+ .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin);
+ mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect);
+ mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin);
+ }
+
+ // Get the deadzone rect between the task views
+ mTaskViewDeadZoneRect.setEmpty();
+ int count = getTaskViewCount();
+ if (count > 0) {
+ final View taskView = getTaskViewAt(0);
+ getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
+ mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
+ taskView.getBottom());
+ }
+ }
+
private void updateEmptyStateUi(boolean sizeChanged) {
boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
if (sizeChanged && hasValidSize) {
mEmptyTextLayout = null;
mLastMeasureSize.set(getWidth(), getHeight());
}
- updateClearAllButtonAlpha();
if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
@@ -1134,16 +1190,16 @@
float toTranslationY = clipAnimationHelper.getSourceRect().centerY()
- clipAnimationHelper.getTargetRect().centerY();
if (launchingCenterTask) {
- TaskView centerTask = getPageAt(centerTaskIndex);
+ TaskView centerTask = getTaskViewAt(centerTaskIndex);
if (taskIndex - 1 >= 0) {
- TaskView adjacentTask = getPageAt(taskIndex - 1);
+ TaskView adjacentTask = getTaskViewAt(taskIndex - 1);
float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
toScale, toTranslationY);
scaleAndTranslation[1] = -scaleAndTranslation[1];
anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
}
- if (taskIndex + 1 < getPageCount()) {
- TaskView adjacentTask = getPageAt(taskIndex + 1);
+ if (taskIndex + 1 < getTaskViewCount()) {
+ TaskView adjacentTask = getTaskViewAt(taskIndex + 1);
float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
toScale, toTranslationY);
anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
@@ -1192,6 +1248,7 @@
TaskViewDrawable drawable = new TaskViewDrawable(tv, this);
getOverlay().add(drawable);
+ final boolean[] passedOverviewThreshold = new boolean[] {false};
ObjectAnimator drawableAnim =
ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0);
drawableAnim.setInterpolator(LINEAR);
@@ -1202,6 +1259,14 @@
animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
? targetSysUiFlags
: 0);
+
+ // Passing the threshold from taskview to fullscreen app will vibrate
+ final boolean passed = animator.getAnimatedFraction() >= MIN_PROGRESS_FOR_OVERVIEW;
+ if (passed != passedOverviewThreshold[0]) {
+ passedOverviewThreshold[0] = passed;
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
});
AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv,
@@ -1229,7 +1294,7 @@
if (task != null) {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
onEndListener.logAction, Direction.DOWN, indexOfChild(tv),
- TaskUtils.getComponentKeyForTask(task.key));
+ TaskUtils.getLaunchComponentKeyForTask(task.key));
}
} else {
onTaskLaunchFinish.accept(false);
@@ -1256,67 +1321,9 @@
return "";
}
- private int additionalScrollForClearAllButton() {
- return (int) getResources().getDimension(
- R.dimen.clear_all_container_width) - getPaddingEnd();
- }
-
- @Override
- protected int computeMaxScrollX() {
- if (getChildCount() == 0) {
- return super.computeMaxScrollX();
- }
-
- // Allow a clear_all_container_width-sized gap after the last task.
- return super.computeMaxScrollX() + (mIsRtl ? 0 : additionalScrollForClearAllButton());
- }
-
- @Override
- protected int offsetForPageScrolls() {
- return mIsRtl ? additionalScrollForClearAllButton() : 0;
- }
-
- public void setClearAllButton(View clearAllButton) {
- mClearAllButton = clearAllButton;
- updateClearAllButtonAlpha();
- }
-
- private void onChildViewsChanged() {
- final int childCount = getChildCount();
- mClearAllButton.setVisibility(childCount == 0 ? INVISIBLE : VISIBLE);
- setFocusable(childCount != 0);
- }
-
- public void revealClearAllButton() {
- setCurrentPage(getChildCount() - 1); // Loads tasks info if needed.
- scrollTo(mIsRtl ? 0 : computeMaxScrollX(), 0);
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (getChildCount() > 0) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
- if (!mIsClearAllButtonFullyRevealed && getCurrentPage() == getPageCount() - 1) {
- revealClearAllButton();
- return true;
- }
- }
- case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
- if (mIsClearAllButtonFullyRevealed) {
- setCurrentPage(getChildCount() - 1);
- return true;
- }
- }
- break;
- }
- }
- return super.performAccessibilityAction(action, arguments);
- }
-
@Override
public void addChildrenForAccessibility(ArrayList<View> outChildren) {
- outChildren.add(mClearAllButton);
+ // Add children in reverse order
for (int i = getChildCount() - 1; i >= 0; --i) {
outChildren.add(getChildAt(i));
}
@@ -1325,17 +1332,9 @@
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
-
- if (getChildCount() > 0) {
- info.addAction(mIsClearAllButtonFullyRevealed ?
- AccessibilityNodeInfo.ACTION_SCROLL_FORWARD :
- AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
- info.setScrollable(true);
- }
-
final AccessibilityNodeInfo.CollectionInfo
collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(
- 1, getChildCount(), false,
+ 1, getTaskViewCount(), false,
AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE);
info.setCollectionInfo(collectionInfo);
}
@@ -1344,15 +1343,14 @@
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- event.setScrollable(getPageCount() > 0);
+ final int taskViewCount = getTaskViewCount();
+ event.setScrollable(taskViewCount > 0);
- if (!mIsClearAllButtonFullyRevealed
- && event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- final int childCount = getChildCount();
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
final int[] visibleTasks = getVisibleChildrenRange();
- event.setFromIndex(childCount - visibleTasks[1] - 1);
- event.setToIndex(childCount - visibleTasks[0] - 1);
- event.setItemCount(childCount);
+ event.setFromIndex(taskViewCount - visibleTasks[1] - 1);
+ event.setToIndex(taskViewCount - visibleTasks[0] - 1);
+ event.setItemCount(taskViewCount);
}
}
@@ -1366,8 +1364,4 @@
protected boolean isPageOrderFlipped() {
return true;
}
-
- public boolean performTaskAccessibilityActionExtra(int action) {
- return false;
- }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
deleted file mode 100644
index c6cd527..0000000
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
-
-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 com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.R;
-
-import java.util.ArrayList;
-
-public class RecentsViewContainer extends InsettableFrameLayout {
- public static final FloatProperty<RecentsViewContainer> CONTENT_ALPHA =
- new FloatProperty<RecentsViewContainer>("contentAlpha") {
- @Override
- public void setValue(RecentsViewContainer view, float v) {
- view.setContentAlpha(v);
- }
-
- @Override
- public Float get(RecentsViewContainer view) {
- return view.mRecentsView.getContentAlpha();
- }
- };
-
- private final Rect mTempRect = new Rect();
-
- private RecentsView mRecentsView;
- private ClearAllButton mClearAllButton;
-
- public RecentsViewContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mClearAllButton = findViewById(R.id.clear_all_button);
- mClearAllButton.setOnClickListener((v) -> {
- mRecentsView.mActivity.getUserEventDispatcher()
- .logActionOnControl(TAP, CLEAR_ALL_BUTTON);
- mRecentsView.dismissAllTasks();
- });
-
- mRecentsView = findViewById(R.id.overview_panel);
- mClearAllButton.forceHasOverlappingRendering(false);
-
- mRecentsView.setClearAllButton(mClearAllButton);
- mClearAllButton.setRecentsView(mRecentsView);
-
- if (mRecentsView.isRtl()) {
- mClearAllButton.setNextFocusRightId(mRecentsView.getId());
- mRecentsView.setNextFocusLeftId(mClearAllButton.getId());
- } else {
- mClearAllButton.setNextFocusLeftId(mRecentsView.getId());
- mRecentsView.setNextFocusRightId(mClearAllButton.getId());
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- mRecentsView.getTaskSize(mTempRect);
-
- mClearAllButton.setTranslationX(
- (mRecentsView.isRtl() ? 1 : -1) *
- (getResources().getDimension(R.dimen.clear_all_container_width)
- - mClearAllButton.getMeasuredWidth()) / 2);
- mClearAllButton.setTranslationY(
- mTempRect.top + (mTempRect.height() - mClearAllButton.getMeasuredHeight()) / 2
- - mClearAllButton.getTop());
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- super.onTouchEvent(ev);
- // Do not let touch escape to siblings below this view. This prevents scrolling of the
- // workspace while in Recents.
- return true;
- }
-
- public void setContentAlpha(float alpha) {
- if (alpha == mRecentsView.getContentAlpha()) {
- return;
- }
- mRecentsView.setContentAlpha(alpha);
- setVisibility(alpha > 0 ? VISIBLE : GONE);
- }
-
- @Override
- public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- if (mRecentsView.getChildCount() > 0) {
- // Carousel is first in tab order.
- views.add(mRecentsView);
- views.add(mClearAllButton);
- }
- }
-
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- return mRecentsView.requestFocus(direction, previouslyFocusedRect) ||
- super.requestFocus(direction, previouslyFocusedRect);
- }
-
- @Override
- public void addChildrenForAccessibility(ArrayList<View> outChildren) {
- outChildren.add(mRecentsView);
- }
-}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index c780b62..8b5e832 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -15,11 +15,10 @@
*/
package com.android.quickstep.views;
-import static android.support.v4.graphics.ColorUtils.compositeColors;
import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
-
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.content.Context;
import android.graphics.Canvas;
@@ -32,7 +31,8 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
@@ -45,22 +45,30 @@
*/
public class ShelfScrimView extends ScrimView {
+ // If the progress is more than this, shelf follows the finger, otherwise it moves faster to
+ // cover the whole screen
+ private static final float SCRIM_CATCHUP_THRESHOLD = 0.2f;
+
// In transposed layout, we simply draw a flat color.
private boolean mDrawingFlatColor;
// For shelf mode
private final int mEndAlpha;
- private final int mThresholdAlpha;
private final float mRadius;
- private final float mMaxScrimAlpha;
+ private final int mMaxScrimAlpha;
private final Paint mPaint;
- // Max vertical progress after which the scrim stops moving.
- private float mMoveThreshold;
- // Minimum visible size of the scrim.
- private int mMinSize;
+ // Mid point where the alpha changes
+ private int mMidAlpha;
+ private float mMidProgress;
- private float mScrimMoveFactor = 0;
+ private float mShiftRange;
+
+ private final float mShelfOffset;
+ private float mTopOffset;
+ private float mShelfTop;
+ private float mShelfTopAtThreshold;
+
private int mShelfColor;
private int mRemainingScreenColor;
@@ -70,13 +78,13 @@
public ShelfScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
- mMaxScrimAlpha = OVERVIEW.getWorkspaceScrimAlpha(mLauncher);
+ mMaxScrimAlpha = Math.round(OVERVIEW.getWorkspaceScrimAlpha(mLauncher) * 255);
mEndAlpha = Color.alpha(mEndScrim);
- mThresholdAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
mRadius = mLauncher.getResources().getDimension(R.dimen.shelf_surface_radius);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mShelfOffset = context.getResources().getDimension(R.dimen.shelf_surface_offset);
// Just assume the easiest UI for now, until we have the proper layout information.
mDrawingFlatColor = true;
}
@@ -93,10 +101,15 @@
mDrawingFlatColor = dp.isVerticalBarLayout();
if (!mDrawingFlatColor) {
- float swipeLength = OverviewState.getDefaultSwipeHeight(mLauncher);
- mMoveThreshold = 1 - swipeLength / mLauncher.getAllAppsController().getShiftRange();
- mMinSize = dp.hotseatBarSizePx + dp.getInsets().bottom;
mRemainingScreenPathValid = false;
+ mShiftRange = mLauncher.getAllAppsController().getShiftRange();
+
+ mMidProgress = OVERVIEW.getVerticalProgress(mLauncher);
+ mMidAlpha = mMidProgress >= 1 ? 0
+ : Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha);
+
+ mTopOffset = dp.getInsets().top - mShelfOffset;
+ mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
updateColors();
}
updateDragHandleAlpha();
@@ -107,82 +120,80 @@
public void updateColors() {
super.updateColors();
if (mDrawingFlatColor) {
+ mDragHandleOffset = 0;
return;
}
- if (mProgress >= mMoveThreshold) {
- mScrimMoveFactor = 1;
-
- if (mProgress >= 1) {
- mShelfColor = 0;
- } else {
- int alpha = Math.round(mThresholdAlpha * ACCEL_2.getInterpolation(
- (1 - mProgress) / (1 - mMoveThreshold)));
- mShelfColor = setAlphaComponent(mEndScrim, alpha);
- }
-
- mRemainingScreenColor = 0;
- } else if (mProgress <= 0) {
- mScrimMoveFactor = 0;
- mShelfColor = mCurrentFlatColor;
- mRemainingScreenColor = 0;
-
+ mDragHandleOffset = mShelfOffset - mDragHandleSize;
+ if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
+ mShelfTop = mShiftRange * mProgress + mTopOffset;
} else {
- mScrimMoveFactor = mProgress / mMoveThreshold;
- mRemainingScreenColor = setAlphaComponent(mScrimColor,
- Math.round((1 - mScrimMoveFactor) * mMaxScrimAlpha * 255));
+ mShelfTop = Utilities.mapRange(mProgress / SCRIM_CATCHUP_THRESHOLD, -mRadius,
+ mShelfTopAtThreshold);
+ }
- // Merge the remainingScreenColor and shelfColor in one to avoid overdraw.
- int alpha = mEndAlpha - Math.round((mEndAlpha - mThresholdAlpha) * mScrimMoveFactor);
- mShelfColor = compositeColors(setAlphaComponent(mEndScrim, alpha),
- mRemainingScreenColor);
+ if (mProgress >= 1) {
+ mRemainingScreenColor = 0;
+ mShelfColor = 0;
+ } else if (mProgress >= mMidProgress) {
+ mRemainingScreenColor = 0;
+
+ int alpha = Math.round(Utilities.mapToRange(
+ mProgress, mMidProgress, 1, mMidAlpha, 0, ACCEL));
+ mShelfColor = setAlphaComponent(mEndScrim, alpha);
+ } else {
+ mDragHandleOffset += mShiftRange * (mMidProgress - mProgress);
+
+ // Note that these ranges and interpolators are inverted because progress goes 1 to 0.
+ int alpha = Math.round(
+ Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
+ (float) mMidAlpha, Interpolators.clampToProgress(ACCEL, 0.5f, 1f)));
+ mShelfColor = setAlphaComponent(mEndScrim, alpha);
+
+ int remainingScrimAlpha = Math.round(
+ Utilities.mapToRange(mProgress, (float) 0, mMidProgress, mMaxScrimAlpha,
+ (float) 0, LINEAR));
+ mRemainingScreenColor = setAlphaComponent(mScrimColor, remainingScrimAlpha);
}
}
@Override
protected void onDraw(Canvas canvas) {
- float translate = drawBackground(canvas);
-
- if (mDragHandle != null) {
- canvas.translate(0, -translate);
- mDragHandle.draw(canvas);
- canvas.translate(0, translate);
- }
+ drawBackground(canvas);
+ drawDragHandle(canvas);
}
- private float drawBackground(Canvas canvas) {
+ private void drawBackground(Canvas canvas) {
if (mDrawingFlatColor) {
if (mCurrentFlatColor != 0) {
canvas.drawColor(mCurrentFlatColor);
}
- return 0;
+ return;
}
- if (mShelfColor == 0) {
- return 0;
- } else if (mScrimMoveFactor <= 0) {
+ if (Color.alpha(mShelfColor) == 0) {
+ return;
+ } else if (mProgress <= 0) {
canvas.drawColor(mShelfColor);
- return getHeight();
+ return;
}
- float minTop = getHeight() - mMinSize;
- float top = minTop * mScrimMoveFactor - mDragHandleSize;
-
+ int height = getHeight();
+ int width = getWidth();
// Draw the scrim over the remaining screen if needed.
if (mRemainingScreenColor != 0) {
if (!mRemainingScreenPathValid) {
mTempPath.reset();
// Using a arbitrary '+10' in the bottom to avoid any left-overs at the
// corners due to rounding issues.
- mTempPath.addRoundRect(0, minTop, getWidth(), getHeight() + mRadius + 10,
+ mTempPath.addRoundRect(0, height - mRadius, width, height + mRadius + 10,
mRadius, mRadius, Direction.CW);
-
mRemainingScreenPath.reset();
- mRemainingScreenPath.addRect(0, 0, getWidth(), getHeight(), Direction.CW);
+ mRemainingScreenPath.addRect(0, 0, width, height, Direction.CW);
mRemainingScreenPath.op(mTempPath, Op.DIFFERENCE);
}
- float offset = minTop - top;
+ float offset = height - mRadius - mShelfTop;
canvas.translate(0, -offset);
mPaint.setColor(mRemainingScreenColor);
canvas.drawPath(mRemainingScreenPath, mPaint);
@@ -190,8 +201,6 @@
}
mPaint.setColor(mShelfColor);
- canvas.drawRoundRect(0, top, getWidth(), getHeight() + mRadius,
- mRadius, mRadius, mPaint);
- return minTop - mDragHandleSize - top;
+ canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index dd90c88..6eb6854 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,19 +16,20 @@
package com.android.quickstep.views;
+import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
+
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Point;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
@@ -37,8 +38,8 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskUtils;
@@ -58,12 +59,14 @@
new TaskSystemShortcut.Install(),
};
- private static final long OPEN_CLOSE_DURATION = 220;
+ private static final int REVEAL_OPEN_DURATION = 150;
+ private static final int REVEAL_CLOSE_DURATION = 100;
private BaseDraggingActivity mActivity;
private TextView mTaskIconAndName;
private AnimatorSet mOpenCloseAnimator;
private TaskView mTaskView;
+ private LinearLayout mOptionLayout;
public TaskMenuView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -73,20 +76,13 @@
super(context, attrs, defStyleAttr);
mActivity = BaseDraggingActivity.fromContext(context);
- setClipToOutline(true);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- float r = getResources().getDimensionPixelSize(R.dimen.task_menu_background_radius);
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), r);
- }
- });
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTaskIconAndName = findViewById(R.id.task_icon_and_name);
+ mOptionLayout = findViewById(R.id.menu_option_layout);
}
@Override
@@ -148,6 +144,12 @@
mTaskIconAndName.setText(TaskUtils.getTitle(getContext(), taskView.getTask()));
mTaskIconAndName.setOnClickListener(v -> close(true));
+ // Move the icon and text up half an icon size to lay over the TaskView
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) mTaskIconAndName.getLayoutParams();
+ params.topMargin = (int) -getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+ mTaskIconAndName.setLayoutParams(params);
+
for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
if (onClickListener != null) {
@@ -157,21 +159,25 @@
}
private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
- DeepShortcutView menuOptionView = (DeepShortcutView) mActivity.getLayoutInflater().inflate(
- R.layout.system_shortcut, this, false);
- menuOptionView.getIconView().setBackgroundResource(menuOption.iconResId);
- menuOptionView.getBubbleText().setText(menuOption.labelResId);
+ ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate(
+ R.layout.task_view_menu_option, this, false);
+ menuOptionView.findViewById(R.id.icon).setBackgroundResource(menuOption.iconResId);
+ ((TextView) menuOptionView.findViewById(R.id.text)).setText(menuOption.labelResId);
menuOptionView.setOnClickListener(onClickListener);
- addView(menuOptionView);
+ mOptionLayout.addView(menuOptionView);
}
private void orientAroundTaskView(TaskView taskView) {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskView, sTempRect);
Rect insets = mActivity.getDragLayer().getInsets();
- int x = sTempRect.left + (sTempRect.width() - getMeasuredWidth()) / 2 - insets.left;
- setX(Utilities.isRtl(getResources()) ? -x : x);
- setY(sTempRect.top - mTaskIconAndName.getPaddingTop() - insets.top);
+ BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
+ params.width = sTempRect.width();
+ params.gravity = Gravity.LEFT;
+ setLayoutParams(params);
+ setX(sTempRect.left - insets.left);
+ setY(sTempRect.top + getResources().getDimension(R.dimen.task_thumbnail_top_margin)
+ - insets.top);
}
private void animateOpen() {
@@ -188,8 +194,13 @@
return;
}
mOpenCloseAnimator = LauncherAnimUtils.createAnimatorSet();
- mOpenCloseAnimator.play(createOpenCloseOutlineProvider()
- .createRevealAnimator(this, closing));
+
+ final Animator revealAnimator = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, closing);
+ revealAnimator.setInterpolator(Interpolators.DEACCEL);
+ mOpenCloseAnimator.play(revealAnimator);
+ mOpenCloseAnimator.play(ObjectAnimator.ofFloat(mTaskView.getThumbnail(), DIM_ALPHA,
+ closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA));
mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
@@ -204,8 +215,7 @@
}
});
mOpenCloseAnimator.play(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
- mOpenCloseAnimator.setDuration(OPEN_CLOSE_DURATION);
- mOpenCloseAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION: REVEAL_OPEN_DURATION);
mOpenCloseAnimator.start();
}
@@ -215,18 +225,9 @@
}
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
- int iconSize = getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
- float fromRadius = iconSize / 2;
- float toRadius = getResources().getDimensionPixelSize(
- R.dimen.task_menu_background_radius);
- Point iconCenter = new Point(getWidth() / 2, mTaskIconAndName.getPaddingTop() + iconSize / 2);
- Rect fromRect = new Rect(iconCenter.x, iconCenter.y, iconCenter.x, iconCenter.y);
+ float radius = getResources().getDimension(R.dimen.task_corner_radius);
+ Rect fromRect = new Rect(0, 0, getWidth(), 0);
Rect toRect = new Rect(0, 0, getWidth(), getHeight());
- return new RoundedRectRevealOutlineProvider(fromRadius, toRadius, fromRect, toRect) {
- @Override
- public boolean shouldRemoveElevationDuringAnimation() {
- return true;
- }
- };
+ return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index d9dfd18..fb653cf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -29,7 +29,6 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
-import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Property;
@@ -68,6 +67,19 @@
}
};
+ public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
+ new FloatProperty<TaskThumbnailView>("dimAlpha") {
+ @Override
+ public void setValue(TaskThumbnailView thumbnail, float dimAlpha) {
+ thumbnail.setDimAlpha(dimAlpha);
+ }
+
+ @Override
+ public Float get(TaskThumbnailView thumbnailView) {
+ return thumbnailView.mDimAlpha;
+ }
+ };
+
private final float mCornerRadius;
private final BaseActivity mActivity;
@@ -149,6 +161,10 @@
updateThumbnailPaintFilter();
}
+ public float getDimAlpha() {
+ return mDimAlpha;
+ }
+
public Rect getInsets() {
if (mThumbnailData != null) {
return mThumbnailData.insets;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b5f31b8..508e5bb 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -71,7 +71,7 @@
* The alpha of a black scrim on a page in the carousel as it leaves the screen.
* In the resting position of the carousel, the adjacent pages have about half this scrim.
*/
- private static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
+ public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
/**
* How much to scale down pages near the edge of the screen.
@@ -118,7 +118,7 @@
launchTask(true /* animate */);
BaseActivity.fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
- TaskUtils.getComponentKeyForTask(getTask().key));
+ TaskUtils.getLaunchComponentKeyForTask(getTask().key));
});
setOutlineProvider(new TaskOutlineProvider(getResources()));
}
@@ -347,8 +347,6 @@
}
}
- if (getRecentsView().performTaskAccessibilityActionExtra(action)) return true;
-
return super.performAccessibilityAction(action, arguments);
}
diff --git a/res/drawable/ic_info_no_shadow.xml b/res/drawable/ic_info_no_shadow.xml
index b5512c3..d816f12 100644
--- a/res/drawable/ic_info_no_shadow.xml
+++ b/res/drawable/ic_info_no_shadow.xml
@@ -16,12 +16,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/textColorPrimary" >
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary">
+
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,17L12,17c0.55,0,1-0.45,1-1v-4c0-0.55-0.45-1-1-1l0,0c-0.55,0-1,0.45-1,1v4C11,16.55,11.45,17,12,17z M12,2C6.48,
- 2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8s3.59-8,8-8s8,3.59,8,8S16.41,20,12,20zM12,
- 9.1L12,9.1c0.61,0,1.1-0.49,1.1-1.1l0,0c0-0.61-0.49-1.1-1.1-1.1l0,0c-0.61,0-1.1,0.49-1.1,1.1l0,0C10.9,8.61,11.39,9.1,12,9.1z"/>
+ android:fillColor="#FFFFFF"
+ android:pathData="M 11 7 H 13 V 9 H 11 V 7 Z" />
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M 11 11 H 13 V 17 H 11 V 11 Z" />
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8 c0-4.41,3.59-8,8-8s8,3.59,8,8C20,16.41,16.41,20,12,20z" />
</vector>
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index da17b2b..304d012 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -39,7 +39,7 @@
launcher:pageIndicator="@+id/page_indicator" />
<include
- android:id="@+id/overview_panel_container"
+ android:id="@+id/overview_panel"
layout="@layout/overview_panel"
android:visibility="gone" />
@@ -48,7 +48,7 @@
<com.android.launcher3.pageindicators.WorkspacePageIndicator
android:id="@+id/page_indicator"
android:layout_width="match_parent"
- android:layout_height="4dp"
+ android:layout_height="@dimen/vertical_drag_handle_size"
android:layout_gravity="bottom|center_horizontal"
android:theme="@style/HomeScreenElementTheme" />
diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml
index b25f46a..e810ab2 100644
--- a/res/values-v26/styles.xml
+++ b/res/values-v26/styles.xml
@@ -21,7 +21,7 @@
<style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
</style>
- <style name="WidgetContainerTheme.Dark" parent="LauncherThemeDark">
+ <style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
<item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
</style>
diff --git a/res/values/config.xml b/res/values/config.xml
index d5bb131..f2d6c21 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -149,4 +149,5 @@
<integer name="config_recentsMaxThumbnailCacheSize">6</integer>
<integer name="config_recentsMaxIconCacheSize">12</integer>
+ <item name="workspace_page_container" type="id" />
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 07e0b04..3bb7a79 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -34,6 +34,8 @@
<!-- Hotseat -->
<dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
<dimen name="dynamic_grid_hotseat_bottom_padding">2dp</dimen>
+ <!-- Extra bottom padding for non-tall devices. -->
+ <dimen name="dynamic_grid_hotseat_bottom_non_tall_padding">0dp</dimen>
<dimen name="dynamic_grid_hotseat_size">80dp</dimen>
<dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
@@ -42,6 +44,7 @@
<dimen name="all_apps_scrim_margin">8dp</dimen>
<dimen name="all_apps_scrim_blur">4dp</dimen>
<dimen name="vertical_drag_handle_size">24dp</dimen>
+ <dimen name="vertical_drag_handle_overlap_workspace">0dp</dimen>
<!-- Drop target bar -->
<dimen name="dynamic_grid_drop_target_size">48dp</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 31cbaa1..07bd800 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -56,7 +56,7 @@
<item name="workspaceStatusBarScrim">@null</item>
</style>
- <style name="LauncherThemeDark" parent="@style/LauncherTheme">
+ <style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
<item name="android:textColorPrimary">#FFFFFFFF</item>
<item name="android:textColorSecondary">#FFFFFFFF</item>
<item name="android:textColorTertiary">#CCFFFFFF</item>
@@ -73,7 +73,7 @@
<item name="isMainColorDark">true</item>
</style>
- <style name="LauncherThemeDark.DarKText" parent="@style/LauncherThemeDark">
+ <style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
<item name="allAppsInterimScrimAlpha">25</item>
<item name="workspaceTextColor">#FF212121</item>
<item name="workspaceShadowColor">@android:color/transparent</item>
@@ -83,6 +83,13 @@
<item name="workspaceStatusBarScrim">@null</item>
</style>
+ <!-- A derivative project can extend these themes to customize the application theme without
+ affecting the base theme -->
+ <style name="AppTheme" parent="@style/LauncherTheme" />
+ <style name="AppTheme.DarkText" parent="@style/LauncherTheme.DarkText" />
+ <style name="AppTheme.Dark" parent="@style/LauncherTheme.Dark" />
+ <style name="AppTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark.DarkText" />
+
<!--
Theme overrides to element on homescreen, i.e., which are drawn on top on wallpaper.
Various foreground colors are overridden to be workspaceTextColor so that they are properly
@@ -115,6 +122,7 @@
<item name="android:saveEnabled">false</item>
<item name="android:textColor">@android:color/white</item>
<item name="android:includeFontPadding">false</item>
+ <item name="android:importantForAccessibility">no</item>
</style>
<!-- Base theme for BubbleTextView and sub classes -->
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index a34f225..ef6e145 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -133,13 +133,13 @@
launcher:name="Nexus 7"
launcher:minWidthDps="575"
launcher:minHeightDps="904"
- launcher:numRows="5"
+ launcher:numRows="6"
launcher:numColumns="6"
launcher:numFolderRows="4"
launcher:numFolderColumns="5"
launcher:iconSize="64"
launcher:iconTextSize="14.4"
- launcher:numHotseatIcons="7"
+ launcher:numHotseatIcons="6"
launcher:defaultLayoutId="@xml/default_workspace_5x6"
/>
@@ -147,8 +147,8 @@
launcher:name="Nexus 10"
launcher:minWidthDps="727"
launcher:minHeightDps="1207"
- launcher:numRows="5"
- launcher:numColumns="6"
+ launcher:numRows="6"
+ launcher:numColumns="7"
launcher:numFolderRows="4"
launcher:numFolderColumns="5"
launcher:iconSize="76"
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index e117deb..a4b6f5b 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -44,13 +44,25 @@
public static final int INVISIBLE_BY_STATE_HANDLER = 1 << 0;
public static final int INVISIBLE_BY_APP_TRANSITIONS = 1 << 1;
+ public static final int INVISIBLE_BY_PENDING_FLAGS = 1 << 2;
+
+ // This is not treated as invisibility flag, but adds as a hint for an incomplete transition.
+ // When the wallpaper animation runs, it replaces this flag with a proper invisibility
+ // flag, INVISIBLE_BY_PENDING_FLAGS only for the duration of that animation.
+ public static final int PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION = 1 << 3;
+
+ private static final int INVISIBLE_FLAGS =
+ INVISIBLE_BY_STATE_HANDLER | INVISIBLE_BY_APP_TRANSITIONS | INVISIBLE_BY_PENDING_FLAGS;
+ public static final int STATE_HANDLER_INVISIBILITY_FLAGS =
+ INVISIBLE_BY_STATE_HANDLER | PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
public static final int INVISIBLE_ALL =
- INVISIBLE_BY_STATE_HANDLER | INVISIBLE_BY_APP_TRANSITIONS;
+ INVISIBLE_FLAGS | PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
@Retention(SOURCE)
@IntDef(
flag = true,
- value = {INVISIBLE_BY_STATE_HANDLER, INVISIBLE_BY_APP_TRANSITIONS})
+ value = {INVISIBLE_BY_STATE_HANDLER, INVISIBLE_BY_APP_TRANSITIONS,
+ INVISIBLE_BY_PENDING_FLAGS, PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION})
public @interface InvisibilityFlags{}
private final ArrayList<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
@@ -208,7 +220,7 @@
/**
* Used to set the override visibility state, used only to handle the transition home with the
* recents animation.
- * @see LauncherAppTransitionManagerImpl.getWallpaperOpenRunner()
+ * @see LauncherAppTransitionManagerImpl#getWallpaperOpenRunner()
*/
public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
mForceInvisible |= flag;
@@ -218,12 +230,15 @@
mForceInvisible &= ~flag;
}
-
/**
* @return Wether this activity should be considered invisible regardless of actual visibility.
*/
public boolean isForceInvisible() {
- return mForceInvisible != 0;
+ return hasSomeInvisibleFlag(INVISIBLE_FLAGS);
+ }
+
+ public boolean hasSomeInvisibleFlag(int mask) {
+ return (mForceInvisible & mask) != 0;
}
public interface MultiWindowModeChangedListener {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 8af9acc..eec196e 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -61,7 +61,7 @@
private OnStartCallback mOnStartCallback;
- private int mThemeRes = R.style.LauncherTheme;
+ private int mThemeRes = R.style.AppTheme;
private DisplayRotationListener mRotationListener;
@@ -91,10 +91,10 @@
protected int getThemeRes(WallpaperColorInfo wallpaperColorInfo) {
if (wallpaperColorInfo.isDark()) {
return wallpaperColorInfo.supportsDarkText() ?
- R.style.LauncherThemeDark_DarKText : R.style.LauncherThemeDark;
+ R.style.AppTheme_Dark_DarkText : R.style.AppTheme_Dark;
} else {
return wallpaperColorInfo.supportsDarkText() ?
- R.style.LauncherTheme_DarkText : R.style.LauncherTheme;
+ R.style.AppTheme_DarkText : R.style.AppTheme;
}
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 2f47728..9839c12 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -59,6 +59,9 @@
private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
+ // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
+ private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
+
// Workspace
public final int desiredWorkspaceLeftRightMarginPx;
public final int cellLayoutPaddingLeftRightPx;
@@ -72,6 +75,7 @@
// Drag handle
public final int verticalDragHandleSizePx;
+ private final int verticalDragHandleOverlapWorkspace;
// Workspace icons
public int iconSizePx;
@@ -101,8 +105,10 @@
// In portrait: size = height, in landscape: size = width
public int hotseatBarSizePx;
public final int hotseatBarTopPaddingPx;
- public final int hotseatBarBottomPaddingPx;
- public final int hotseatBarSidePaddingPx;
+ public int hotseatBarBottomPaddingPx;
+ // Start is the side next to the nav bar, end is the side next to the workspace
+ public final int hotseatBarSidePaddingStartPx;
+ public final int hotseatBarSidePaddingEndPx;
// All apps
public int allAppsCellHeightPx;
@@ -133,6 +139,17 @@
this.isLandscape = isLandscape;
this.isMultiWindowMode = isMultiWindowMode;
+ // Determine sizes.
+ widthPx = width;
+ heightPx = height;
+ if (isLandscape) {
+ availableWidthPx = maxSize.x;
+ availableHeightPx = minSize.y;
+ } else {
+ availableWidthPx = minSize.x;
+ availableHeightPx = maxSize.y;
+ }
+
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
@@ -140,6 +157,8 @@
isTablet = res.getBoolean(R.bool.is_tablet);
isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
isPhone = !isTablet && !isLargeTablet;
+ float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
+ boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
// Some more constants
transposeLayoutWithOrientation =
@@ -156,12 +175,16 @@
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
- cellLayoutPaddingLeftRightPx =
+ int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
+ ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
+ cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier *
res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
cellLayoutBottomPaddingPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
verticalDragHandleSizePx = res.getDimensionPixelSize(
R.dimen.vertical_drag_handle_size);
+ verticalDragHandleOverlapWorkspace =
+ res.getDimensionPixelSize(R.dimen.vertical_drag_handle_overlap_workspace);
defaultPageSpacingPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
topWorkspacePadding =
@@ -176,39 +199,33 @@
hotseatBarTopPaddingPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
- hotseatBarBottomPaddingPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
- hotseatBarSidePaddingPx =
+ hotseatBarBottomPaddingPx = (isTallDevice ? 0
+ : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+ hotseatBarSidePaddingEndPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
+ // Add a bit of space between nav bar and hotseat in multi-window vertical bar layout.
+ hotseatBarSidePaddingStartPx = isMultiWindowMode && isVerticalBarLayout()
+ ? edgeMarginPx : 0;
hotseatBarSizePx = isVerticalBarLayout()
- ? Utilities.pxFromDp(inv.iconSize, dm)
+ ? Utilities.pxFromDp(inv.iconSize, dm) + hotseatBarSidePaddingStartPx
+ + hotseatBarSidePaddingEndPx
: res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_size)
+ hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx;
- // Determine sizes.
- widthPx = width;
- heightPx = height;
- if (isLandscape) {
- availableWidthPx = maxSize.x;
- availableHeightPx = minSize.y;
- } else {
- availableWidthPx = minSize.x;
- availableHeightPx = maxSize.y;
- }
-
// Calculate all of the remaining variables.
updateAvailableDimensions(dm, res);
// Now that we have all of the variables calculated, we can tune certain sizes.
- float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
- boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
if (!isVerticalBarLayout() && isPhone && isTallDevice) {
// We increase the hotseat size when there is extra space.
// ie. For a display with a large aspect ratio, we can keep the icons on the workspace
// in portrait mode closer together by adding more height to the hotseat.
// Note: This calculation was created after noticing a pattern in the design spec.
- int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx;
- hotseatBarSizePx += extraSpace - verticalDragHandleSizePx;
+ int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
+ - verticalDragHandleSizePx;
+ hotseatBarSizePx += extraSpace;
+ hotseatBarBottomPaddingPx += extraSpace;
// Recalculate the available dimensions using the new hotseat size.
updateAvailableDimensions(dm, res);
@@ -326,7 +343,8 @@
// Hotseat
if (isVerticalLayout) {
- hotseatBarSizePx = iconSizePx;
+ hotseatBarSizePx = iconSizePx + hotseatBarSidePaddingStartPx
+ + hotseatBarSidePaddingEndPx;
}
hotseatCellHeightPx = iconSizePx;
@@ -425,17 +443,16 @@
if (isVerticalBarLayout()) {
padding.top = 0;
padding.bottom = edgeMarginPx;
- padding.left = hotseatBarSidePaddingPx;
- padding.right = hotseatBarSidePaddingPx;
if (isSeascape()) {
- padding.left += hotseatBarSizePx;
- padding.right += verticalDragHandleSizePx;
+ padding.left = hotseatBarSizePx;
+ padding.right = verticalDragHandleSizePx;
} else {
- padding.left += verticalDragHandleSizePx;
- padding.right += hotseatBarSizePx;
+ padding.left = verticalDragHandleSizePx;
+ padding.right = hotseatBarSizePx;
}
} else {
- int paddingBottom = hotseatBarSizePx + verticalDragHandleSizePx;
+ int paddingBottom = hotseatBarSizePx + verticalDragHandleSizePx
+ - verticalDragHandleOverlapWorkspace;
if (isTablet) {
// Pad the left and right of the workspace to ensure consistent spacing
// between all icons
@@ -462,11 +479,11 @@
public Rect getHotseatLayoutPadding() {
if (isVerticalBarLayout()) {
if (isSeascape()) {
- mHotseatPadding.set(
- mInsets.left, mInsets.top, hotseatBarSidePaddingPx, mInsets.bottom);
+ mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
+ mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom);
} else {
- mHotseatPadding.set(
- hotseatBarSidePaddingPx, mInsets.top, mInsets.right, mInsets.bottom);
+ mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
+ mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
}
} else {
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 403c8b8..4e0f2e7 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -99,6 +99,10 @@
mShowImeAfterFirstLayout = !showSoftInput();
}
+ public void hideKeyboard() {
+ UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
+ }
+
private boolean showSoftInput() {
return requestFocus() &&
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
@@ -106,7 +110,7 @@
}
public void dispatchBackKey() {
- UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
+ hideKeyboard();
if (mBackKeyListener != null) {
mBackKeyListener.onBackKey();
}
@@ -135,6 +139,6 @@
nextFocus.requestFocus();
}
}
- UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
+ hideKeyboard();
}
}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 1b91e88..9217ca9 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -261,7 +261,7 @@
/**
* Updates the paint to reflect the current brightness and saturation.
*/
- private void updateFilter() {
+ protected void updateFilter() {
boolean usePorterDuffFilter = false;
int key = -1;
if (mDesaturation > 0) {
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index ee4b113..6668f2c 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -157,10 +157,10 @@
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
if (grid.isSeascape()) {
lp.gravity = Gravity.LEFT;
- lp.width = grid.hotseatBarSizePx + insets.left + grid.hotseatBarSidePaddingPx;
+ lp.width = grid.hotseatBarSizePx + insets.left;
} else {
lp.gravity = Gravity.RIGHT;
- lp.width = grid.hotseatBarSizePx + insets.right + grid.hotseatBarSidePaddingPx;
+ lp.width = grid.hotseatBarSizePx + insets.right;
}
} else {
lp.gravity = Gravity.BOTTOM;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 8d79737..c5ca183 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -801,7 +801,7 @@
}
private static final class IconDB extends SQLiteCacheHelper {
- private final static int RELEASE_VERSION = 22;
+ private final static int RELEASE_VERSION = 24;
private final static String TABLE_NAME = "icons";
private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3eaead1..4fe3503 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -214,8 +214,6 @@
// UI and state for the overview panel
private View mOverviewPanel;
- private View mOverviewPanelContainer;
-
@Thunk boolean mWorkspaceLoading = true;
private OnResumeCallback mOnResumeCallback;
@@ -350,6 +348,12 @@
}
@Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ UiFactory.onEnterAnimationComplete(this);
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
int diff = newConfig.diff(mOldConfig);
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
@@ -743,6 +747,8 @@
NotificationListener.removeNotificationsChangedListener();
getStateManager().moveToRestState();
+ UiFactory.onLauncherStateOrResumeChanged(this);
+
// Workaround for b/78520668, explicitly trim memory once UI is hidden
onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
}
@@ -913,7 +919,6 @@
mWorkspace = mDragLayer.findViewById(R.id.workspace);
mWorkspace.initParentViews(mDragLayer);
mOverviewPanel = findViewById(R.id.overview_panel);
- mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
mHotseat = findViewById(R.id.hotseat);
mHotseatSearchBox = findViewById(R.id.search_container_hotseat);
@@ -1174,10 +1179,6 @@
return (T) mOverviewPanel;
}
- public <T extends View> T getOverviewPanelContainer() {
- return (T) mOverviewPanelContainer;
- }
-
public DropTargetBar getDropTargetBar() {
return mDropTargetBar;
}
@@ -2335,6 +2336,8 @@
if (isInState(NORMAL)) {
shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
+ shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text),
+ KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
}
final View currentFocus = getCurrentFocus();
if (currentFocus != null) {
@@ -2383,6 +2386,12 @@
return true;
}
break;
+ case KeyEvent.KEYCODE_W:
+ if (isInState(NORMAL)) {
+ OptionsPopupView.openWidgets(this);
+ return true;
+ }
+ break;
}
}
return super.onKeyShortcut(keyCode, event);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 04a32f7..37538ae 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -640,6 +640,7 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
ShortcutInfo info = shortcutProvider.get();
+ getModelWriter().updateItemInDatabase(info);
ArrayList<ShortcutInfo> update = new ArrayList<>();
update.add(info);
bindUpdatedShortcuts(update, info.user);
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 5b010dc..8a15b24 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -242,8 +242,10 @@
* Called when the start transition ends and the user settles on this particular state.
*/
public void onStateTransitionEnd(Launcher launcher) {
- if (this == NORMAL) {
+ if (this == NORMAL || this == SPRING_LOADED) {
UiFactory.resetOverview(launcher);
+ }
+ if (this == NORMAL) {
// Clear any rotation locks when going to normal state
launcher.getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 05c515b..3c7c1aa 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -298,6 +298,24 @@
/**
* Creates a {@link AnimatorPlaybackController} that can be used for a controlled
+ * state transition. The UI is force-set to fromState before creating the controller.
+ * @param fromState the initial state for the transition.
+ * @param state the final state for the transition.
+ * @param duration intended duration for normal playback. Use higher duration for better
+ * accuracy.
+ */
+ public AnimatorPlaybackController createAnimationToNewWorkspace(
+ LauncherState fromState, LauncherState state, long duration) {
+ mConfig.reset();
+ for (StateHandler handler : getStateHandlers()) {
+ handler.setState(fromState);
+ }
+
+ return createAnimationToNewWorkspace(state, duration);
+ }
+
+ /**
+ * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
* state transition.
* @param state the final state for the transition.
* @param duration intended duration for normal playback. Use higher duration for better
@@ -348,12 +366,6 @@
}
@Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- mState = mCurrentStableState;
- }
-
- @Override
public void onAnimationSuccess(Animator animator) {
// Run any queued runnables
if (onCompleteRunnable != null) {
@@ -370,7 +382,9 @@
}
private void onStateTransitionStart(LauncherState state) {
- mState.onStateDisabled(mLauncher);
+ if (mState != state) {
+ mState.onStateDisabled(mLauncher);
+ }
mState = state;
mState.onStateEnabled(mLauncher);
mLauncher.getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
@@ -438,6 +452,7 @@
}
public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
+ clearCurrentAnimation();
setCurrentAnimation(controller.getTarget());
mConfig.userControlled = true;
mConfig.playbackController = controller;
@@ -538,6 +553,9 @@
@Override
public void onAnimationEnd(Animator animation) {
+ if (playbackController != null && playbackController.getTarget() == animation) {
+ playbackController = null;
+ }
if (mCurrentAnimation == animation) {
mCurrentAnimation = null;
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index de9cd98..db5dc66 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -24,7 +24,6 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
import android.provider.Settings;
@@ -142,8 +141,6 @@
protected T mPageIndicator;
// Convenience/caching
- private static final Matrix sTmpInvMatrix = new Matrix();
- private static final float[] sTmpPoint = new float[2];
private static final Rect sTmpRect = new Rect();
protected final Rect mInsets = new Rect();
@@ -242,12 +239,6 @@
return index;
}
- protected void scrollAndForceFinish(int scrollX) {
- scrollTo(scrollX, 0);
- mScroller.setFinalX(scrollX);
- forceFinishScroller(true);
- }
-
/**
* Updates the scroll of the current page immediately to its final scroll position. We use this
* in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
@@ -259,7 +250,9 @@
if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
newX = getScrollForPage(mCurrentPage);
}
- scrollAndForceFinish(newX);
+ scrollTo(newX, 0);
+ mScroller.setFinalX(newX);
+ forceFinishScroller(true);
}
private void abortScrollerAnimation(boolean resetNextPage) {
@@ -544,10 +537,6 @@
setMeasuredDimension(widthSize, heightSize);
}
- protected void restoreScrollOnLayout() {
- setCurrentPage(getNextPage());
- }
-
@SuppressLint("DrawAllocation")
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
@@ -599,7 +588,7 @@
}
if (mScroller.isFinished() && pageScrollChanged) {
- restoreScrollOnLayout();
+ setCurrentPage(getNextPage());
}
}
@@ -620,23 +609,26 @@
- mInsets.bottom - getPaddingBottom()) / 2;
final int scrollOffsetLeft = mInsets.left + getPaddingLeft();
+ final int scrollOffsetRight = getWidth() - getPaddingRight() - mInsets.right;
boolean pageScrollChanged = false;
- for (int i = startIndex, childLeft = scrollOffsetLeft + offsetForPageScrolls();
- i != endIndex;
- i += delta) {
+ for (int i = startIndex, childLeft = scrollOffsetLeft; i != endIndex; i += delta) {
final View child = getPageAt(i);
if (scrollLogic.shouldIncludeView(child)) {
- final int childTop = verticalCenter - child.getMeasuredHeight() / 2;
final int childWidth = child.getMeasuredWidth();
+ final int childRight = childLeft + childWidth;
if (layoutChildren) {
final int childHeight = child.getMeasuredHeight();
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(), childTop + childHeight);
+ final int childTop = verticalCenter - childHeight / 2;
+ child.layout(childLeft, childTop, childRight, childTop + childHeight);
}
- final int pageScroll = childLeft - scrollOffsetLeft;
+ // In case the pages are of different width, align the page to left or right edge
+ // based on the orientation.
+ final int pageScroll = mIsRtl
+ ? (childLeft - scrollOffsetLeft)
+ : Math.max(0, childRight - scrollOffsetRight);
if (outPageScrolls[i] != pageScroll) {
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
@@ -666,10 +658,6 @@
}
}
- protected int offsetForPageScrolls() {
- return 0;
- }
-
public void setPageSpacing(int pageSpacing) {
mPageSpacing = pageSpacing;
requestLayout();
@@ -747,11 +735,13 @@
if (direction == View.FOCUS_LEFT) {
if (getCurrentPage() > 0) {
snapToPage(getCurrentPage() - 1);
+ getChildAt(getCurrentPage() - 1).requestFocus(direction);
return true;
}
} else if (direction == View.FOCUS_RIGHT) {
if (getCurrentPage() < getPageCount() - 1) {
snapToPage(getCurrentPage() + 1);
+ getChildAt(getCurrentPage() + 1).requestFocus(direction);
return true;
}
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b2f8146..0e6d567 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -48,6 +48,7 @@
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;
+import android.view.animation.Interpolator;
import com.android.launcher3.config.FeatureFlags;
@@ -273,6 +274,25 @@
return scale;
}
+ /**
+ * Maps t from one range to another range.
+ * @param t The value to map.
+ * @param fromMin The lower bound of the range that t is being mapped from.
+ * @param fromMax The upper bound of the range that t is being mapped from.
+ * @param toMin The lower bound of the range that t is being mapped to.
+ * @param toMax The upper bound of the range that t is being mapped to.
+ * @return The mapped value of t.
+ */
+ public static float mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax,
+ Interpolator interpolator) {
+ if (fromMin == fromMax || toMin == toMax) {
+ Log.e(TAG, "mapToRange: range has 0 length");
+ return toMin;
+ }
+ float progress = Math.abs(t - fromMin) / Math.abs(fromMax - fromMin);
+ return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
+ }
+
public static float mapRange(float value, float min, float max) {
return min + (value * (max - min));
}
@@ -464,6 +484,13 @@
}
/**
+ * @see #boundToRange(int, int, int).
+ */
+ public static long boundToRange(long value, long lowerBound, long upperBound) {
+ return Math.max(lowerBound, Math.min(value, upperBound));
+ }
+
+ /**
* Wraps a message with a TTS span, so that a different message is spoken than
* what is getting displayed.
* @param msg original message
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 66fb3c6..54d29c1 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -286,7 +286,9 @@
mInsets.set(insets);
DeviceProfile grid = mLauncher.getDeviceProfile();
- mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
+ mMaxDistanceForFolderCreation = grid.isTablet
+ ? 0.75f * grid.iconSizePx
+ : 0.55f * grid.iconSizePx;
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
Rect padding = grid.workspacePadding;
@@ -547,6 +549,7 @@
// created CellLayout.
CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
+ newScreen.getShortcutsAndWidgets().setId(R.id.workspace_page_container);
int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
@@ -2386,7 +2389,12 @@
private void manageFolderFeedback(CellLayout targetLayout,
int[] targetCell, float distance, DragObject dragObject) {
- if (distance > mMaxDistanceForFolderCreation) return;
+ if (distance > mMaxDistanceForFolderCreation) {
+ if (mDragMode != DRAG_MODE_NONE) {
+ setDragMode(DRAG_MODE_NONE);
+ }
+ return;
+ }
final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
ItemInfo info = dragObject.dragInfo;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 72ba418..fdf32af 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -312,6 +312,12 @@
}
}
+ @Override
+ public int getCanvasClipTopForOverscroll() {
+ // Do not clip if the QSB is attached to the spring, otherwise the QSB will get clipped.
+ return mSpringViews.get(getSearchView().getId()) ? 0 : mHeader.getTop();
+ }
+
private void rebindAdapters(boolean showTabs) {
rebindAdapters(showTabs, false /* force */);
}
@@ -490,6 +496,12 @@
});
}
+ @Override
+ public void getDrawingRect(Rect outRect) {
+ super.getDrawingRect(outRect);
+ outRect.offset(0, (int) getTranslationY());
+ }
+
public class AdapterHolder {
public static final int MAIN = 0;
public static final int WORK = 1;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index ccd5586..24a8d51 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -5,6 +5,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
@@ -151,7 +152,7 @@
@Override
public void setState(LauncherState state) {
setProgress(state.getVerticalProgress(mLauncher));
- setAlphas(state, NO_ANIM_PROPERTY_SETTER);
+ setAlphas(state, null, new AnimatorSetBuilder());
onProgressAnimationEnd();
}
@@ -164,7 +165,7 @@
AnimatorSetBuilder builder, AnimationConfig config) {
float targetProgress = toState.getVerticalProgress(mLauncher);
if (Float.compare(mProgress, targetProgress) == 0) {
- setAlphas(toState, config.getPropertySetter(builder));
+ setAlphas(toState, config, builder);
// Fail fast
onProgressAnimationEnd();
return;
@@ -186,19 +187,24 @@
builder.play(anim);
- setAlphas(toState, config.getPropertySetter(builder));
+ setAlphas(toState, config, builder);
}
- private void setAlphas(LauncherState toState, PropertySetter setter) {
+ private void setAlphas(LauncherState toState, AnimationConfig config,
+ AnimatorSetBuilder builder) {
+ PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
+ : config.getPropertySetter(builder);
int visibleElements = toState.getVisibleElements(mLauncher);
boolean hasHeader = (visibleElements & ALL_APPS_HEADER) != 0;
boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
- setter.setViewAlpha(mAppsView.getSearchView(), hasHeader ? 1 : 0, LINEAR);
- setter.setViewAlpha(mAppsView.getContentView(), hasContent ? 1 : 0, LINEAR);
- setter.setViewAlpha(mAppsView.getScrollBar(), hasContent ? 1 : 0, LINEAR);
- mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasContent, setter);
+ Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
+ setter.setViewAlpha(mAppsView.getSearchView(), hasHeader ? 1 : 0, allAppsFade);
+ setter.setViewAlpha(mAppsView.getContentView(), hasContent ? 1 : 0, allAppsFade);
+ setter.setViewAlpha(mAppsView.getScrollBar(), hasContent ? 1 : 0, allAppsFade);
+ mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasContent, setter,
+ allAppsFade);
setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
(visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, LINEAR);
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 462e7f3..eaa7774 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
@@ -28,6 +26,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import com.android.launcher3.R;
@@ -227,8 +226,9 @@
p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
}
- public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter) {
- setter.setViewAlpha(this, hasContent ? 1 : 0, LINEAR);
+ public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter,
+ Interpolator fadeInterpolator) {
+ setter.setViewAlpha(this, hasContent ? 1 : 0, fadeInterpolator);
allowTouchForwarding(hasContent);
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index e83904f..dcc4554 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -21,6 +21,8 @@
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -37,7 +39,8 @@
* An interface to a search box that AllApps can command.
*/
public class AllAppsSearchBarController
- implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener {
+ implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
+ OnFocusChangeListener {
protected Launcher mLauncher;
protected Callbacks mCb;
@@ -62,6 +65,7 @@
mInput.addTextChangedListener(this);
mInput.setOnEditorActionListener(this);
mInput.setOnBackKeyListener(this);
+ mInput.setOnFocusChangeListener(this);
mSearchAlgorithm = searchAlgorithm;
}
@@ -123,6 +127,13 @@
return false;
}
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (!hasFocus) {
+ mInput.hideKeyboard();
+ }
+ }
+
/**
* Resets the search bar state.
*/
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 84085cb..164728a 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.anim;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
@@ -72,7 +74,7 @@
mOnCancelRunnable = onCancelRunnable;
mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
- mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
+ mAnimationPlayer.setInterpolator(LINEAR);
mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
mAnimationPlayer.addUpdateListener(this);
@@ -107,6 +109,10 @@
return mDuration;
}
+ public TimeInterpolator getInterpolator() {
+ return mAnim.getInterpolator() != null ? mAnim.getInterpolator() : LINEAR;
+ }
+
/**
* Starts playing the animation forward from current position.
*/
@@ -202,6 +208,19 @@
}
}
+ public void dispatchSetInterpolator(TimeInterpolator interpolator) {
+ dispatchSetInterpolatorRecursively(mAnim, interpolator);
+ }
+
+ private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) {
+ anim.setInterpolator(interpolator);
+ if (anim instanceof AnimatorSet) {
+ for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) {
+ dispatchSetInterpolatorRecursively(child, interpolator);
+ }
+ }
+ }
+
public void setOnCancelRunnable(Runnable runnable) {
mOnCancelRunnable = runnable;
}
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index f10bce8..307f258 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -35,6 +35,7 @@
public static final int ANIM_WORKSPACE_FADE = 2;
public static final int ANIM_OVERVIEW_SCALE = 3;
public static final int ANIM_OVERVIEW_FADE = 4;
+ public static final int ANIM_ALL_APPS_FADE = 5;
protected final ArrayList<Animator> mAnims = new ArrayList<>();
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index bace7df..675e26d 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,7 +16,10 @@
package com.android.launcher3.anim;
+import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+
import android.graphics.Path;
+import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -24,6 +27,8 @@
import android.view.animation.OvershootInterpolator;
import android.view.animation.PathInterpolator;
+import com.android.launcher3.Utilities;
+
/**
* Common interpolators used in Launcher
@@ -43,6 +48,8 @@
public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
+ public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
+
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f);
@@ -50,6 +57,9 @@
public static final Interpolator EXAGGERATED_EASE;
+ private static final int MIN_SETTLE_DURATION = 200;
+ private static final float OVERSHOOT_FACTOR = 0.9f;
+
static {
Path exaggeratedEase = new Path();
exaggeratedEase.moveTo(0, 0);
@@ -117,6 +127,14 @@
}
/**
+ * Create an OvershootInterpolator with tension directly related to the velocity (in px/ms).
+ * @param velocity The start velocity of the animation we want to overshoot.
+ */
+ public static Interpolator overshootInterpolatorForVelocity(float velocity) {
+ return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f));
+ }
+
+ /**
* Runs the given interpolator such that the entire progress is set between the given bounds.
* That is, we set the interpolation to 0 until lowerBound and reach 1 by upperBound.
*/
@@ -135,4 +153,85 @@
return interpolator.getInterpolation((t - lowerBound) / (upperBound - lowerBound));
};
}
-}
\ No newline at end of file
+
+ /**
+ * Runs the given interpolator such that the interpolated value is mapped to the given range.
+ * This is useful, for example, if we only use this interpolator for part of the animation,
+ * such as to take over a user-controlled animation when they let go.
+ */
+ public static Interpolator mapToProgress(Interpolator interpolator, float lowerBound,
+ float upperBound) {
+ return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound);
+ }
+
+ /**
+ * Computes parameters necessary for an overshoot effect.
+ */
+ public static class OvershootParams {
+ public Interpolator interpolator;
+ public float start;
+ public float end;
+ public long duration;
+
+ /**
+ * Given the input params, sets OvershootParams variables to be used by the caller.
+ * @param startProgress The progress from 0 to 1 that the overshoot starts from.
+ * @param overshootPastProgress The progress from 0 to 1 where we overshoot past (should
+ * either be equal to startProgress or endProgress, depending on if we want to
+ * overshoot immediately or only once we reach the end).
+ * @param endProgress The final progress from 0 to 1 that we will settle to.
+ * @param velocityPxPerMs The initial velocity that causes this overshoot.
+ * @param totalDistancePx The distance against which progress is calculated.
+ */
+ public OvershootParams(float startProgress, float overshootPastProgress,
+ float endProgress, float velocityPxPerMs, int totalDistancePx) {
+ velocityPxPerMs = Math.abs(velocityPxPerMs);
+ start = startProgress;
+ int startPx = (int) (start * totalDistancePx);
+ // Overshoot by about half a frame.
+ float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
+ SINGLE_FRAME_MS / totalDistancePx / 2;
+ overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
+ end = overshootPastProgress + overshootBy;
+ int endPx = (int) (end * totalDistancePx);
+ int overshootDistance = endPx - startPx;
+ // Calculate deceleration necessary to reach overshoot distance.
+ // Formula: velocityFinal^2 = velocityInitial^2 + 2 * acceleration * distance
+ // 0 = v^2 + 2ad (velocityFinal == 0)
+ // a = v^2 / -2d
+ float decelerationPxPerMs = velocityPxPerMs * velocityPxPerMs / (2 * overshootDistance);
+ // Calculate time necessary to reach peak of overshoot.
+ // Formula: acceleration = velocity / time
+ // time = velocity / acceleration
+ duration = (long) (velocityPxPerMs / decelerationPxPerMs);
+
+ // Now that we're at the top of the overshoot, need to settle back to endProgress.
+ float settleDistance = end - endProgress;
+ int settleDistancePx = (int) (settleDistance * totalDistancePx);
+ // Calculate time necessary for the settle.
+ // Formula: distance = velocityInitial * time + 1/2 * acceleration * time^2
+ // d = 1/2at^2 (velocityInitial = 0, since we just stopped at the top)
+ // t = sqrt(2d/a)
+ // Above formula assumes constant acceleration. Since we use ACCEL_DEACCEL, we actually
+ // have acceleration to halfway then deceleration the rest. So the formula becomes:
+ // t = sqrt(d/a) * 2 (half the distance for accel, half for deaccel)
+ long settleDuration = (long) Math.sqrt(settleDistancePx / decelerationPxPerMs) * 4;
+
+ settleDuration = Math.max(MIN_SETTLE_DURATION, settleDuration);
+ // How much of the animation to devote to playing the overshoot (the rest is for settle).
+ float overshootFraction = (float) duration / (duration + settleDuration);
+ duration += settleDuration;
+ // Finally, create the interpolator, composed of two interpolators: an overshoot, which
+ // reaches end > 1, and then a settle to endProgress.
+ Interpolator overshoot = Interpolators.clampToProgress(DEACCEL, 0, overshootFraction);
+ // The settle starts at 1, where 1 is the top of the overshoot, and maps to a fraction
+ // such that final progress is endProgress. For example, if we overshot to 1.1 but want
+ // to end at 1, we need to map to 1/1.1.
+ Interpolator settle = Interpolators.clampToProgress(Interpolators.mapToProgress(
+ ACCEL_DEACCEL, 1, (endProgress - start) / (end - start)), overshootFraction, 1);
+ interpolator = t -> t <= overshootFraction
+ ? overshoot.getInterpolation(t)
+ : settle.getInterpolation(t);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 89ba72a..333fe59 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -182,7 +182,12 @@
* The bitmap is also visually normalized with other icons.
*/
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
- return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
+ return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false, null);
+ }
+
+ public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
+ boolean isInstantApp) {
+ return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
}
/**
@@ -191,8 +196,10 @@
* The bitmap is also visually normalized with other icons.
*/
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
- boolean isInstantApp) {
- float[] scale = new float[1];
+ boolean isInstantApp, float [] scale) {
+ if (scale == null) {
+ scale = new float[1];
+ }
icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
Bitmap bitmap = createIconBitmap(icon, scale[0]);
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
@@ -241,7 +248,8 @@
private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
RectF outIconBounds, float[] outScale) {
float scale = 1f;
- if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+ if ((Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) ||
+ Utilities.ATLEAST_P) {
boolean[] outShape = new boolean[1];
if (mWrapperIcon == null) {
mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
@@ -332,7 +340,7 @@
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int) Math.ceil(BLUR_FACTOR * textureWidth), Math.max(left, top));
int size = Math.max(width, height);
- icon.setBounds(offset, offset, offset + size, offset + size);
+ icon.setBounds(offset, offset, size - offset, size - offset);
} else {
icon.setBounds(left, top, left+width, top+height);
}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 42ba191..d3a7955 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -77,7 +77,7 @@
private final Matrix mTmpMatrix = new Matrix();
private final PathMeasure mPathMeasure = new PathMeasure();
- private final Context mContext;
+ private final ItemInfoWithIcon mItem;
// Path in [0, 100] bounds.
private final Path mProgressPath;
@@ -106,7 +106,7 @@
*/
public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) {
super(info);
- mContext = context;
+ mItem = info;
mProgressPath = progressPath;
mScaledTrackPath = new Path();
mScaledProgressPath = new Path();
@@ -274,7 +274,7 @@
mTrackAlpha = MAX_PAINT_ALPHA;
setIsDisabled(true);
} else if (progress >= 1) {
- setIsDisabled(false);
+ setIsDisabled(mItem.isDisabled());
mScaledTrackPath.set(mScaledProgressPath);
float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 5fbf502..88da853 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -126,13 +126,13 @@
}
public Bitmap createPill(int width, int height) {
- radius = height / 2;
+ radius = height / 2f;
- int centerX = Math.round(width / 2 + shadowBlur);
+ int centerX = Math.round(width / 2f + shadowBlur);
int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
int center = Math.max(centerX, centerY);
bounds.set(0, 0, width, height);
- bounds.offsetTo(center - width / 2, center - height / 2);
+ bounds.offsetTo(center - width / 2f, center - height / 2f);
int size = center * 2;
Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 089303e..977dcd7 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -18,7 +18,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -42,6 +41,9 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.BitmapInfo;
import com.android.launcher3.graphics.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LongArrayMap;
@@ -52,6 +54,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
/**
* Handles updates due to changes in package manager (app installed/updated/removed)
@@ -162,12 +165,7 @@
final ArrayMap<ComponentName, AppInfo> addedOrUpdatedApps = new ArrayMap<>();
if (!addedOrModified.isEmpty()) {
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindAppsAddedOrUpdated(addedOrModified);
- }
- });
+ scheduleCallbackTask((callbacks) -> callbacks.bindAppsAddedOrUpdated(addedOrModified));
for (AppInfo ai : addedOrModified) {
addedOrUpdatedApps.put(ai.componentName, ai);
}
@@ -213,11 +211,26 @@
}
if (si.isPromise() && isNewApkAvailable) {
+ boolean isTargetValid = true;
+ if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ List<ShortcutInfoCompat> shortcut = DeepShortcutManager
+ .getInstance(context).queryForPinnedShortcuts(
+ cn.getPackageName(),
+ Arrays.asList(si.getDeepShortcutId()), mUser);
+ if (shortcut.isEmpty()) {
+ isTargetValid = false;
+ } else {
+ si.updateFromDeepShortcutInfo(shortcut.get(0), context);
+ infoUpdated = true;
+ }
+ } else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
+ isTargetValid = LauncherAppsCompat.getInstance(context)
+ .isActivityEnabledForProfile(cn, mUser);
+ }
+
if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINSTALL_ICON)) {
// Auto install icon
- LauncherAppsCompat launcherApps
- = LauncherAppsCompat.getInstance(context);
- if (!launcherApps.isActivityEnabledForProfile(cn, mUser)) {
+ if (!isTargetValid) {
// Try to find the best match activity.
Intent intent = new PackageManagerHelper(context)
.getAppLaunchIntent(cn.getPackageName(), mUser);
@@ -235,6 +248,11 @@
continue;
}
}
+ } else if (!isTargetValid) {
+ removedShortcuts.put(si.id, true);
+ FileLog.e(TAG, "Restored shortcut no longer valid "
+ + si.intent);
+ continue;
} else {
si.status = ShortcutInfo.DEFAULT;
infoUpdated = true;
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 33caded..5c0e259 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -116,6 +116,10 @@
*/
public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
mNotificationInfo = mainNotification;
+ NotificationListener listener = NotificationListener.getInstanceIfConnected();
+ if (listener != null) {
+ listener.setNotificationsShown(new String[] {mNotificationInfo.notificationKey});
+ }
CharSequence title = mNotificationInfo.title;
CharSequence text = mNotificationInfo.text;
if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(text)) {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 9098777..be666a6 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -266,11 +266,7 @@
}
// Insets are added later, so subtract them now.
- if (mIsRtl) {
- x += insets.right;
- } else {
- x -= insets.left;
- }
+ x -= insets.left;
y -= insets.top;
mGravity = 0;
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 3c1cc90..693e532 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -75,6 +75,7 @@
public View.OnClickListener getOnClickListener(
BaseDraggingActivity activity, ItemInfo itemInfo) {
return (view) -> {
+ dismissTaskMenuView(activity);
Rect sourceBounds = activity.getViewBounds(view);
Bundle opts = activity.getActivityLaunchOptionsAsBundle(view);
new PackageManagerHelper(activity).startDetailsActivityForInfo(
@@ -117,4 +118,9 @@
};
}
}
+
+ protected static void dismissTaskMenuView(BaseDraggingActivity activity) {
+ AbstractFloatingView.closeOpenViews(activity, true,
+ AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+ }
}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index b26d39f..5b8ae58 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -16,6 +16,10 @@
package com.android.launcher3.qsb;
+import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_BIND;
+import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
+import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_PROVIDER;
+
import android.app.Activity;
import android.app.Fragment;
import android.app.SearchManager;
@@ -74,11 +78,12 @@
/**
* A fragment to display the QSB.
*/
- public static class QsbFragment extends Fragment implements View.OnClickListener {
+ public static class QsbFragment extends Fragment {
+ public static final int QSB_WIDGET_HOST_ID = 1026;
private static final int REQUEST_BIND_QSB = 1;
- private static final String QSB_WIDGET_ID = "qsb_widget_id";
+ protected String mKeyWidgetId = "qsb_widget_id";
private QsbWidgetHost mQsbWidgetHost;
private AppWidgetProviderInfo mWidgetInfo;
private QsbWidgetHostView mQsb;
@@ -90,10 +95,15 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mQsbWidgetHost = new QsbWidgetHost(getActivity());
+ mQsbWidgetHost = createHost();
mOrientation = getContext().getResources().getConfiguration().orientation;
}
+ protected QsbWidgetHost createHost() {
+ return new QsbWidgetHost(getActivity(), QSB_WIDGET_HOST_ID,
+ (c) -> new QsbWidgetHostView(c));
+ }
+
private FrameLayout mWrapper;
@Override
@@ -110,24 +120,16 @@
}
private View createQsb(ViewGroup container) {
- Activity activity = getActivity();
- mWidgetInfo = getSearchWidgetProvider(activity);
+ mWidgetInfo = getSearchWidgetProvider();
if (mWidgetInfo == null) {
// There is no search provider, just show the default widget.
- return QsbWidgetHostView.getDefaultView(container);
+ return getDefaultView(container, false /* show setup icon */);
}
-
+ Bundle opts = createBindOptions();
+ Activity activity = getActivity();
AppWidgetManager widgetManager = AppWidgetManager.getInstance(activity);
- InvariantDeviceProfile idp = LauncherAppState.getIDP(activity);
- Bundle opts = new Bundle();
- Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(activity, idp.numColumns, 1, null);
- opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
- opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
- opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
- opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
-
- int widgetId = Utilities.getPrefs(activity).getInt(QSB_WIDGET_ID, -1);
+ int widgetId = Utilities.getPrefs(activity).getInt(mKeyWidgetId, -1);
AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
boolean isWidgetBound = (widgetInfo != null) &&
widgetInfo.provider.equals(mWidgetInfo.provider);
@@ -160,38 +162,23 @@
.getAppWidgetOptions(widgetId), opts)) {
mQsb.updateAppWidgetOptions(opts);
}
- mQsb.setPadding(0, 0, 0, 0);
mQsbWidgetHost.startListening();
return mQsb;
}
// Return a default widget with setup icon.
- View v = QsbWidgetHostView.getDefaultView(container);
- View setupButton = v.findViewById(R.id.btn_qsb_setup);
- setupButton.setVisibility(View.VISIBLE);
- setupButton.setOnClickListener(this);
- return v;
+ return getDefaultView(container, true /* show setup icon */);
}
private void saveWidgetId(int widgetId) {
- Utilities.getPrefs(getActivity()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
- }
-
- @Override
- public void onClick(View view) {
- // Start intent for bind the widget
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
- // Allocate a new widget id for QSB
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mQsbWidgetHost.allocateAppWidgetId());
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
- startActivityForResult(intent, REQUEST_BIND_QSB);
+ Utilities.getPrefs(getActivity()).edit().putInt(mKeyWidgetId, widgetId).apply();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_BIND_QSB) {
if (resultCode == Activity.RESULT_OK) {
- saveWidgetId(data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1));
+ saveWidgetId(data.getIntExtra(EXTRA_APPWIDGET_ID, -1));
rebindFragment();
} else {
mQsbWidgetHost.deleteHost();
@@ -228,48 +215,83 @@
public boolean isQsbEnabled() {
return FeatureFlags.QSB_ON_FIRST_SCREEN;
}
- }
- /**
- * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
- * provided by the same package which is set to be global search activity.
- * If widgetCategory is not supported, or no such widget is found, returns the first widget
- * provided by the package.
- */
- public static AppWidgetProviderInfo getSearchWidgetProvider(Context context) {
- SearchManager searchManager =
- (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
- ComponentName searchComponent = searchManager.getGlobalSearchActivity();
- if (searchComponent == null) return null;
- String providerPkg = searchComponent.getPackageName();
+ protected Bundle createBindOptions() {
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(getActivity());
- AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
+ Bundle opts = new Bundle();
+ Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(getActivity(),
+ idp.numColumns, 1, null);
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
+ return opts;
+ }
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
- if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
- if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
- return info;
- } else if (defaultWidgetForSearchPackage == null) {
- defaultWidgetForSearchPackage = info;
+ protected View getDefaultView(ViewGroup container, boolean showSetupIcon) {
+ // Return a default widget with setup icon.
+ View v = QsbWidgetHostView.getDefaultView(container);
+ if (showSetupIcon) {
+ View setupButton = v.findViewById(R.id.btn_qsb_setup);
+ setupButton.setVisibility(View.VISIBLE);
+ setupButton.setOnClickListener((v2) -> startActivityForResult(
+ new Intent(ACTION_APPWIDGET_BIND)
+ .putExtra(EXTRA_APPWIDGET_ID, mQsbWidgetHost.allocateAppWidgetId())
+ .putExtra(EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider),
+ REQUEST_BIND_QSB));
+ }
+ return v;
+ }
+
+ /**
+ * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
+ * provided by the same package which is set to be global search activity.
+ * If widgetCategory is not supported, or no such widget is found, returns the first widget
+ * provided by the package.
+ */
+ protected AppWidgetProviderInfo getSearchWidgetProvider() {
+ SearchManager searchManager =
+ (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
+ ComponentName searchComponent = searchManager.getGlobalSearchActivity();
+ if (searchComponent == null) return null;
+ String providerPkg = searchComponent.getPackageName();
+
+ AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
+
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getActivity());
+ for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
+ if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
+ if ((info.widgetCategory
+ & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
+ return info;
+ } else if (defaultWidgetForSearchPackage == null) {
+ defaultWidgetForSearchPackage = info;
+ }
}
}
+ return defaultWidgetForSearchPackage;
}
- return defaultWidgetForSearchPackage;
}
- private static class QsbWidgetHost extends AppWidgetHost {
+ public static class QsbWidgetHost extends AppWidgetHost {
- private static final int QSB_WIDGET_HOST_ID = 1026;
+ private final WidgetViewFactory mViewFactory;
- public QsbWidgetHost(Context context) {
- super(context, QSB_WIDGET_HOST_ID);
+ public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) {
+ super(context, hostId);
+ mViewFactory = viewFactory;
}
@Override
protected AppWidgetHostView onCreateView(
Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
- return new QsbWidgetHostView(context);
+ return mViewFactory.newView(context);
}
}
+
+ public interface WidgetViewFactory {
+
+ QsbWidgetHostView newView(Context context);
+ }
}
diff --git a/src/com/android/launcher3/qsb/QsbWidgetHostView.java b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
index 7d8a4db..cff5126 100644
--- a/src/com/android/launcher3/qsb/QsbWidgetHostView.java
+++ b/src/com/android/launcher3/qsb/QsbWidgetHostView.java
@@ -52,19 +52,20 @@
return mPreviousOrientation != orientation;
}
+ @Override
+ public void setPadding(int left, int top, int right, int bottom) {
+ // Prevent the base class from applying the default widget padding.
+ super.setPadding(0, 0, 0, 0);
+ }
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
try {
super.onLayout(changed, left, top, right, bottom);
} catch (final RuntimeException e) {
- post(new Runnable() {
- @Override
- public void run() {
- // Update the widget with 0 Layout id, to reset the view to error view.
- updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
- }
- });
+ // Update the widget with 0 Layout id, to reset the view to error view.
+ post(() -> updateAppWidget(
+ new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0)));
}
}
@@ -76,24 +77,16 @@
@Override
protected View getDefaultView() {
View v = super.getDefaultView();
- v.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- Launcher.getLauncher(getContext()).startSearch("", false, null, true);
- }
- });
+ v.setOnClickListener((v2) ->
+ Launcher.getLauncher(getContext()).startSearch("", false, null, true));
return v;
}
public static View getDefaultView(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.qsb_default_view, parent, false);
- v.findViewById(R.id.btn_qsb_search).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- Launcher.getLauncher(view.getContext()).startSearch("", false, null, true);
- }
- });
+ v.findViewById(R.id.btn_qsb_search).setOnClickListener((v2) ->
+ Launcher.getLauncher(v2.getContext()).startSearch("", false, null, true));
return v;
}
}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index f44f5c8..24e2e2f 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -181,7 +181,12 @@
* If packageName is null, returns all pinned shortcuts regardless of package.
*/
public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandle user) {
- return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user);
+ return queryForPinnedShortcuts(packageName, null, user);
+ }
+
+ public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
+ List<String> shortcutIds, UserHandle user) {
+ return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, shortcutIds, user);
}
public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandle user) {
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index cf7c6ba..c6370c5 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -96,10 +96,12 @@
private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null);
private MainThreadExecutor mMainThreadExecutor;
- public synchronized void schedule(InternalStateHandler handler) {
- mPendingHandler = new WeakReference<>(handler);
- if (mMainThreadExecutor == null) {
- mMainThreadExecutor = new MainThreadExecutor();
+ public void schedule(InternalStateHandler handler) {
+ synchronized (this) {
+ mPendingHandler = new WeakReference<>(handler);
+ if (mMainThreadExecutor == null) {
+ mMainThreadExecutor = new MainThreadExecutor();
+ }
}
mMainThreadExecutor.execute(this);
}
@@ -118,23 +120,25 @@
initIfPending(launcher, launcher.isStarted());
}
- public synchronized boolean initIfPending(Launcher launcher, boolean alreadyOnHome) {
+ public boolean initIfPending(Launcher launcher, boolean alreadyOnHome) {
InternalStateHandler pendingHandler = mPendingHandler.get();
if (pendingHandler != null) {
if (!pendingHandler.init(launcher, alreadyOnHome)) {
- mPendingHandler.clear();
+ clearReference(pendingHandler);
}
return true;
}
return false;
}
- public synchronized boolean clearReference(InternalStateHandler handler) {
- if (mPendingHandler.get() == handler) {
- mPendingHandler.clear();
- return true;
+ public boolean clearReference(InternalStateHandler handler) {
+ synchronized (this) {
+ if (mPendingHandler.get() == handler) {
+ mPendingHandler.clear();
+ return true;
+ }
+ return false;
}
- return false;
}
public boolean hasPending() {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 453810c..55f850c 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -29,6 +29,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.os.SystemClock;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@@ -85,7 +86,11 @@
private boolean mCanBlockFling;
private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
- private AnimatorSet mAtomicAnim;
+ protected AnimatorSet mAtomicAnim;
+ // True if we want to resume playing atomic components when mAtomicAnim completes.
+ private boolean mScheduleResumeAtomicComponent;
+ private AutoPlayAtomicAnimationInfo mAtomicAnimAutoPlayInfo;
+
private boolean mPassedOverviewAtomicThreshold;
// mAtomicAnim plays the atomic components of the state animations when we pass the threshold.
// However, if we reinit to transition to a new state (e.g. OVERVIEW -> ALL_APPS) before the
@@ -93,6 +98,8 @@
// interfere with the atomic animation. When the atomic animation ends, we start controlling
// the atomic components as well, using this controller.
private AnimatorPlaybackController mAtomicComponentsController;
+ private LauncherState mAtomicComponentsTargetState = NORMAL;
+
private float mAtomicComponentsStartProgress;
public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
@@ -191,27 +198,21 @@
}
int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
? NON_ATOMIC_COMPONENT : ANIM_ALL;
+ mScheduleResumeAtomicComponent = false;
if (mAtomicAnim != null) {
+ animComponents = NON_ATOMIC_COMPONENT;
// Control the non-atomic components until the atomic animation finishes, then control
// the atomic components as well.
- animComponents = NON_ATOMIC_COMPONENT;
- mAtomicAnim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animation) {
- cancelAtomicComponentsController();
- if (mCurrentAnimation != null) {
- mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction();
- long duration = (long) (getShiftRange() * 2);
- mAtomicComponentsController = AnimatorPlaybackController.wrap(
- createAtomicAnimForState(mFromState, mToState, duration), duration);
- mAtomicComponentsController.dispatchOnStart();
- }
- }
- });
+ mScheduleResumeAtomicComponent = true;
}
- if (goingBetweenNormalAndOverview(mFromState, mToState)) {
+ if (goingBetweenNormalAndOverview(mFromState, mToState)
+ || mAtomicComponentsTargetState != mToState) {
cancelAtomicComponentsController();
}
+
+ if (mAtomicComponentsController != null) {
+ animComponents &= ~ATOMIC_COMPONENT;
+ }
mProgressMultiplier = initCurrentAnimation(animComponents);
mCurrentAnimation.dispatchOnStart();
return true;
@@ -236,12 +237,17 @@
if (mCurrentAnimation == null) {
mFromState = mStartState;
mToState = null;
- mAtomicComponentsController = null;
+ cancelAnimationControllers();
reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
mDisplacementShift = 0;
} else {
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
+
+ mAtomicAnimAutoPlayInfo = null;
+ if (mAtomicComponentsController != null) {
+ mAtomicComponentsController.pause();
+ }
}
mCanBlockFling = mFromState == NORMAL;
mFlingBlockCheck.unblockFling();
@@ -277,7 +283,9 @@
protected void updateProgress(float fraction) {
mCurrentAnimation.setPlayFraction(fraction);
if (mAtomicComponentsController != null) {
- mAtomicComponentsController.setPlayFraction(fraction - mAtomicComponentsStartProgress);
+ // Make sure we don't divide by 0, and have at least a small runway.
+ float start = Math.min(mAtomicComponentsStartProgress, 0.9f);
+ mAtomicComponentsController.setPlayFraction((fraction - start) / (1 - start));
}
maybeUpdateAtomicAnim(mFromState, mToState, fraction);
}
@@ -302,20 +310,40 @@
mAtomicAnim.cancel();
}
mAtomicAnim = createAtomicAnimForState(atomicFromState, atomicToState, ATOMIC_DURATION);
- mAtomicAnim.addListener(new AnimatorListenerAdapter() {
+ mAtomicAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
mAtomicAnim = null;
+ mScheduleResumeAtomicComponent = false;
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (!mScheduleResumeAtomicComponent) {
+ return;
+ }
+ cancelAtomicComponentsController();
+
+ if (mCurrentAnimation != null) {
+ mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction();
+ long duration = (long) (getShiftRange() * 2);
+ mAtomicComponentsController = AnimatorPlaybackController.wrap(
+ createAtomicAnimForState(mFromState, mToState, duration), duration);
+ mAtomicComponentsController.dispatchOnStart();
+ mAtomicComponentsTargetState = mToState;
+ maybeAutoPlayAtomicComponentsAnim();
+ }
}
});
mAtomicAnim.start();
- mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
+ mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
}
private AnimatorSet createAtomicAnimForState(LauncherState fromState, LauncherState targetState,
long duration) {
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ AnimatorSetBuilder builder = getAnimatorSetBuilderForStates(fromState, targetState);
mLauncher.getStateManager().prepareForAtomicAnimation(fromState, targetState, builder);
AnimationConfig config = new AnimationConfig();
config.animComponents = ATOMIC_COMPONENT;
@@ -326,6 +354,11 @@
return builder.build();
}
+ protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
+ LauncherState toState) {
+ return new AnimatorSetBuilder();
+ }
+
@Override
public void onDragEnd(float velocity, boolean fling) {
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
@@ -337,6 +370,8 @@
final LauncherState targetState;
final float progress = mCurrentAnimation.getProgressFraction();
+ final float interpolatedProgress = mCurrentAnimation.getInterpolator()
+ .getInterpolation(progress);
if (fling) {
targetState =
Float.compare(Math.signum(velocity), Math.signum(mProgressMultiplier)) == 0
@@ -345,7 +380,7 @@
} else {
float successProgress = mToState == ALL_APPS
? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
- targetState = (progress > successProgress) ? mToState : mFromState;
+ targetState = (interpolatedProgress > successProgress) ? mToState : mFromState;
}
final float endProgress;
@@ -397,16 +432,8 @@
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
}
anim.start();
- if (mAtomicAnim == null) {
- startAtomicComponentsAnim(endProgress, anim.getDuration());
- } else {
- mAtomicAnim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- startAtomicComponentsAnim(endProgress, anim.getDuration());
- }
- });
- }
+ mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, anim.getDuration());
+ maybeAutoPlayAtomicComponentsAnim();
}
/**
@@ -416,18 +443,32 @@
* the non-atomic components, which only happens if we reinit before the atomic animation
* finishes.
*/
- private void startAtomicComponentsAnim(float toProgress, long duration) {
- if (mAtomicComponentsController != null) {
- ValueAnimator atomicAnim = mAtomicComponentsController.getAnimationPlayer();
- atomicAnim.setFloatValues(mAtomicComponentsController.getProgressFraction(), toProgress);
- atomicAnim.setDuration(duration);
+ private void maybeAutoPlayAtomicComponentsAnim() {
+ if (mAtomicComponentsController == null || mAtomicAnimAutoPlayInfo == null) {
+ return;
+ }
+
+ final AnimatorPlaybackController controller = mAtomicComponentsController;
+ ValueAnimator atomicAnim = controller.getAnimationPlayer();
+ atomicAnim.setFloatValues(controller.getProgressFraction(),
+ mAtomicAnimAutoPlayInfo.toProgress);
+ long duration = mAtomicAnimAutoPlayInfo.endTime - SystemClock.elapsedRealtime();
+ mAtomicAnimAutoPlayInfo = null;
+ if (duration <= 0) {
atomicAnim.start();
+ atomicAnim.end();
+ mAtomicComponentsController = null;
+ } else {
+ atomicAnim.setDuration(duration);
atomicAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mAtomicComponentsController = null;
+ if (mAtomicComponentsController == controller) {
+ mAtomicComponentsController = null;
+ }
}
});
+ atomicAnim.start();
}
}
@@ -457,7 +498,11 @@
}
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
- clearState();
+ if (mAtomicComponentsController != null) {
+ mAtomicComponentsController.getAnimationPlayer().end();
+ mAtomicComponentsController = null;
+ }
+ cancelAnimationControllers();
boolean shouldGoToTargetState = true;
if (mPendingAnimation != null) {
boolean reachedTarget = mToState == targetState;
@@ -484,6 +529,15 @@
}
protected void clearState() {
+ cancelAnimationControllers();
+ if (mAtomicAnim != null) {
+ mAtomicAnim.cancel();
+ mAtomicAnim = null;
+ }
+ mScheduleResumeAtomicComponent = false;
+ }
+
+ private void cancelAnimationControllers() {
mCurrentAnimation = null;
cancelAtomicComponentsController();
mDetector.finishedScrolling();
@@ -495,5 +549,17 @@
mAtomicComponentsController.getAnimationPlayer().cancel();
mAtomicComponentsController = null;
}
+ mAtomicAnimAutoPlayInfo = null;
+ }
+
+ private static class AutoPlayAtomicAnimationInfo {
+
+ public final float toProgress;
+ public final long endTime;
+
+ AutoPlayAtomicAnimationInfo(float toProgress, long duration) {
+ this.toProgress = toProgress;
+ this.endTime = duration + SystemClock.elapsedRealtime();
+ }
}
}
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 2489d30..611931d 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -21,37 +21,102 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
/**
* {@link BroadcastReceiver} which watches configuration changes and
* restarts the process in case changes which affect the device profile occur.
*/
-public class ConfigMonitor extends BroadcastReceiver {
+public class ConfigMonitor extends BroadcastReceiver implements DisplayListener {
+
+ private static final String TAG = "ConfigMonitor";
+
+ private final Point mTmpPoint1 = new Point();
+ private final Point mTmpPoint2 = new Point();
private final Context mContext;
private final float mFontScale;
private final int mDensity;
+ private final int mDisplayId;
+ private final Point mRealSize;
+ private final Point mSmallestSize, mLargestSize;
+
public ConfigMonitor(Context context) {
mContext = context;
Configuration config = context.getResources().getConfiguration();
mFontScale = config.fontScale;
mDensity = config.densityDpi;
+
+ Display display = getDefaultDisplay(context);
+ mDisplayId = display.getDisplayId();
+
+ mRealSize = new Point();
+ display.getRealSize(mRealSize);
+
+ mSmallestSize = new Point();
+ mLargestSize = new Point();
+ display.getCurrentSizeRange(mSmallestSize, mLargestSize);
}
@Override
public void onReceive(Context context, Intent intent) {
Configuration config = context.getResources().getConfiguration();
if (mFontScale != config.fontScale || mDensity != config.densityDpi) {
- Log.d("ConfigMonitor", "Configuration changed, restarting launcher");
- mContext.unregisterReceiver(this);
- android.os.Process.killProcess(android.os.Process.myPid());
+ Log.d(TAG, "Configuration changed");
+ killProcess();
}
}
public void register() {
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+ mContext.getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ Display display = getDefaultDisplay(mContext);
+ display.getRealSize(mTmpPoint1);
+
+ if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) {
+ Log.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1));
+ killProcess();
+ return;
+ }
+
+ display.getCurrentSizeRange(mTmpPoint1, mTmpPoint2);
+ if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) {
+ Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
+ mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2));
+ killProcess();
+ }
+ }
+
+ private void killProcess() {
+ Log.d(TAG, "restarting launcher");
+ mContext.unregisterReceiver(this);
+ mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
+ private Display getDefaultDisplay(Context context) {
+ return context.getSystemService(WindowManager.class).getDefaultDisplay();
}
}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index c17857f..db4c492 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -153,7 +153,10 @@
}
public static boolean onWidgetsClicked(View view) {
- Launcher launcher = Launcher.getLauncher(view.getContext());
+ return openWidgets(Launcher.getLauncher(view.getContext()));
+ }
+
+ public static boolean openWidgets(Launcher launcher) {
if (launcher.getPackageManager().isSafeMode()) {
Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
return false;
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 6e3ef07..7066980 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -109,6 +109,7 @@
protected int mEndFlatColorAlpha;
protected final int mDragHandleSize;
+ protected float mDragHandleOffset;
private final Rect mDragHandleBounds;
private final RectF mHitRect = new RectF();
@@ -223,8 +224,14 @@
if (mCurrentFlatColor != 0) {
canvas.drawColor(mCurrentFlatColor);
}
+ drawDragHandle(canvas);
+ }
+
+ protected void drawDragHandle(Canvas canvas) {
if (mDragHandle != null) {
+ canvas.translate(0, -mDragHandleOffset);
mDragHandle.draw(canvas);
+ canvas.translate(0, mDragHandleOffset);
}
}
@@ -237,20 +244,23 @@
final Drawable drawable = mDragHandle;
mDragHandle = null;
- drawable.setBounds(mDragHandleBounds);
- Rect topBounds = new Rect(mDragHandleBounds);
- topBounds.offset(0, -mDragHandleBounds.height() / 2);
+ Rect bounds = new Rect(mDragHandleBounds);
+ bounds.offset(0, -(int) mDragHandleOffset);
+ drawable.setBounds(bounds);
- Rect invalidateRegion = new Rect(mDragHandleBounds);
+ Rect topBounds = new Rect(bounds);
+ topBounds.offset(0, -bounds.height() / 2);
+
+ Rect invalidateRegion = new Rect(bounds);
invalidateRegion.top = topBounds.top;
Keyframe frameTop = Keyframe.ofObject(0.6f, topBounds);
frameTop.setInterpolator(DEACCEL);
- Keyframe frameBot = Keyframe.ofObject(1, mDragHandleBounds);
+ Keyframe frameBot = Keyframe.ofObject(1, bounds);
frameBot.setInterpolator(ACCEL);
PropertyValuesHolder holder = PropertyValuesHolder .ofKeyframe("bounds",
- Keyframe.ofObject(0, mDragHandleBounds), frameTop, frameBot);
+ Keyframe.ofObject(0, bounds), frameTop, frameBot);
holder.setEvaluator(new RectEvaluator());
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder);
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
index 5022d65..b0313ce 100644
--- a/src/com/android/launcher3/views/SpringRelativeLayout.java
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -54,7 +54,7 @@
}
};
- private final SparseBooleanArray mSpringViews = new SparseBooleanArray();
+ protected final SparseBooleanArray mSpringViews = new SparseBooleanArray();
private final SpringAnimation mSpring;
private float mDampedScrollShift = 0;
@@ -85,12 +85,24 @@
invalidate();
}
+ /**
+ * Used to clip the canvas when drawing child views during overscroll.
+ */
+ public int getCanvasClipTopForOverscroll() {
+ return 0;
+ }
+
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) {
+ int saveCount = canvas.save();
+
+ canvas.clipRect(0, getCanvasClipTopForOverscroll(), getWidth(), getHeight());
canvas.translate(0, mDampedScrollShift);
boolean result = super.drawChild(canvas, child, drawingTime);
- canvas.translate(0, -mDampedScrollShift);
+
+ canvas.restoreToCount(saveCount);
+
return result;
}
return super.drawChild(canvas, child, drawingTime);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 5a7e50f..0d727fd 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -48,6 +48,8 @@
public static void onStart(Launcher launcher) { }
+ public static void onEnterAnimationComplete(Context context) {}
+
public static void onLauncherStateOrResumeChanged(Launcher launcher) { }
public static void onTrimMemory(Launcher launcher, int level) { }
diff --git a/tests/Android.mk b/tests/Android.mk
index e8797a7..f6f02fe 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -22,7 +22,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest-common.xml
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 28
LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3Tests
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 011aa22..f16f514 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -33,6 +33,7 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
+import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.LauncherAppState;
@@ -64,6 +65,7 @@
public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
+ public static final long SHORT_UI_TIMEOUT= 300;
public static final long DEFAULT_UI_TIMEOUT = 3000;
public static final long LARGE_UI_TIMEOUT = 10000;
public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
@@ -73,6 +75,8 @@
protected Context mTargetContext;
protected String mTargetPackage;
+ private static final String TAG = "AbstractLauncherUiTest";
+
@Before
public void setUp() throws Exception {
mDevice = UiDevice.getInstance(getInstrumentation());
@@ -119,8 +123,7 @@
protected UiObject2 openWidgetsTray() {
mDevice.pressMenu(); // Enter overview mode.
mDevice.wait(Until.findObject(
- By.text(mTargetContext.getString(R.string.widget_button_text)
- .toUpperCase(Locale.getDefault()))), DEFAULT_UI_TIMEOUT).click();
+ By.text(mTargetContext.getString(R.string.widget_button_text))), DEFAULT_UI_TIMEOUT).click();
return findViewById(R.id.widgets_list_view);
}
@@ -130,6 +133,8 @@
*/
protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
do {
+ // findObject can only execute after spring settles.
+ mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
UiObject2 widget = container.findObject(condition);
if (widget != null) {
return widget;
@@ -140,6 +145,7 @@
/**
* Drags an icon to the center of homescreen.
+ * @param icon object that is either app icon or shortcut icon
*/
protected void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
Point center = icon.getVisibleCenter();
@@ -250,6 +256,7 @@
public LauncherAppWidgetProviderInfo call() throws Exception {
ComponentName cn = new ComponentName(getInstrumentation().getContext(),
hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
+ Log.d(TAG, "findWidgetProvider componentName=" + cn.flattenToString());
return AppWidgetManagerCompat.getInstance(mTargetContext)
.findProvider(cn, Process.myUserHandle());
}
@@ -271,7 +278,13 @@
protected LauncherActivityInfo getSettingsApp() {
return LauncherAppsCompat.getInstance(mTargetContext)
- .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
+ .getActivityList("com.android.settings",
+ Process.myUserHandle()).get(0);
+ }
+
+ protected LauncherActivityInfo getChromeApp() {
+ return LauncherAppsCompat.getInstance(mTargetContext)
+ .getActivityList("com.android.chrome", Process.myUserHandle()).get(0);
}
/**
diff --git a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
index 46343a3..b95a850 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsAppLaunchTest.java
@@ -41,15 +41,15 @@
private void performTest() throws Exception {
mActivityMonitor.startLauncher();
- LauncherActivityInfo settingsApp = getSettingsApp();
+ LauncherActivityInfo testApp = getChromeApp();
// Open all apps and wait for load complete
final UiObject2 appsContainer = openAllApps();
assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
- // Open settings app and verify app launched
- scrollAndFind(appsContainer, By.text(settingsApp.getLabel().toString())).click();
+ // Open app and verify app launched
+ scrollAndFind(appsContainer, By.text(testApp.getLabel().toString())).click();
assertTrue(mDevice.wait(Until.hasObject(By.pkg(
- settingsApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT));
+ testApp.getComponentName().getPackageName()).depth(0)), DEFAULT_UI_TIMEOUT));
}
}
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
index a40ad7f..69f6c87 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -46,14 +46,15 @@
private void performTest() throws Exception {
mActivityMonitor.startLauncher();
- LauncherActivityInfo settingsApp = getSettingsApp();
+ LauncherActivityInfo testApp = getSettingsApp();
// Open all apps and wait for load complete
final UiObject2 appsContainer = openAllApps();
- assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2),
+ DEFAULT_UI_TIMEOUT));
// Find settings app and verify shortcuts appear when long pressed
- UiObject2 icon = scrollAndFind(appsContainer, By.text(settingsApp.getLabel().toString()));
+ UiObject2 icon = scrollAndFind(appsContainer, By.text(testApp.getLabel().toString()));
// Press icon center until shortcuts appear
Point iconCenter = icon.getVisibleCenter();
sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
@@ -63,11 +64,13 @@
// Verify that launching a shortcut opens a page with the same text
assertTrue(deepShortcutsContainer.getChildCount() > 0);
- UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+
+ // Pick second children as it starts showing shortcuts.
+ UiObject2 shortcut = deepShortcutsContainer.getChildren().get(1)
.findObject(getSelectorForId(R.id.bubble_text));
shortcut.click();
assertTrue(mDevice.wait(Until.hasObject(By.pkg(
- settingsApp.getComponentName().getPackageName())
+ testApp.getComponentName().getPackageName())
.text(shortcut.getText())), DEFAULT_UI_TIMEOUT));
}
}
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
index 434311d..fad06a6 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
@@ -48,14 +48,15 @@
clearHomescreen();
mActivityMonitor.startLauncher();
- LauncherActivityInfo settingsApp = getSettingsApp();
+ LauncherActivityInfo testApp = getSettingsApp();
// Open all apps and wait for load complete.
final UiObject2 appsContainer = openAllApps();
- assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2),
+ DEFAULT_UI_TIMEOUT));
// Find the app and long press it to show shortcuts.
- UiObject2 icon = scrollAndFind(appsContainer, By.text(settingsApp.getLabel().toString()));
+ UiObject2 icon = scrollAndFind(appsContainer, By.text(testApp.getLabel().toString()));
// Press icon center until shortcuts appear
Point iconCenter = icon.getVisibleCenter();
sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
@@ -65,7 +66,7 @@
// Drag the first shortcut to the home screen.
assertTrue(deepShortcutsContainer.getChildCount() > 0);
- UiObject2 shortcut = deepShortcutsContainer.getChildren().get(0)
+ UiObject2 shortcut = deepShortcutsContainer.getChildren().get(1)
.findObject(getSelectorForId(R.id.bubble_text));
String shortcutName = shortcut.getText();
dragToWorkspace(shortcut, false);
@@ -74,7 +75,7 @@
// (the app opens and has the same text as the shortcut).
mDevice.findObject(By.text(shortcutName)).click();
assertTrue(mDevice.wait(Until.hasObject(By.pkg(
- settingsApp.getComponentName().getPackageName())
+ testApp.getComponentName().getPackageName())
.text(shortcutName)), DEFAULT_UI_TIMEOUT));
}
}
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index ccee7da..6244434 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -68,12 +68,15 @@
// Open all apps and wait for load complete
final UiObject2 appsContainer = openAllApps();
- assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+ assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2),
+ LARGE_UI_TIMEOUT));
+ /*
assertTrue("Personal tab is missing",
mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_personal)),
LARGE_UI_TIMEOUT));
assertTrue("Work tab is missing",
mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_work)), LARGE_UI_TIMEOUT));
+ */
}
}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 32f90a6..b557119 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -49,6 +49,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -122,7 +123,7 @@
setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
}
- @Test
+ @Test @Ignore
public void testUnboundWidget_removed() throws Exception {
LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
@@ -177,7 +178,7 @@
LauncherSettings.Favorites.APPWIDGET_ID))));
}
- @Test
+ @Test @Ignore
public void testPendingWidget_notRestored_removed() throws Exception {
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index bd21315..dcb564a 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -45,6 +45,7 @@
import com.android.launcher3.widget.WidgetCell;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -79,6 +80,9 @@
}
@Test
+ public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ }
+
+ @Test @Ignore
public void testPinWidgetNoConfig() throws Throwable {
runTest("pinWidgetNoConfig", true, new ItemOperator() {
@Override
@@ -91,7 +95,7 @@
});
}
- @Test
+ @Test @Ignore
public void testPinWidgetNoConfig_customPreview() throws Throwable {
// Command to set custom preview
Intent command = RequestPinItemActivity.getCommandIntent(
@@ -109,7 +113,7 @@
}, command);
}
- @Test
+ @Test @Ignore
public void testPinWidgetWithConfig() throws Throwable {
runTest("pinWidgetWithConfig", true, new ItemOperator() {
@Override
@@ -122,7 +126,7 @@
});
}
- @Test
+ @Test @Ignore
public void testPinShortcut() throws Throwable {
// Command to set the shortcut id
Intent command = RequestPinItemActivity.getCommandIntent(
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
deleted file mode 100644
index 691d9bc..0000000
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ /dev/null
@@ -1,255 +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.
- */
-
-package com.android.launcher3.util;
-
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.KeyEvent;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Tests the {@link FocusLogic} class that handles key event based focus handling.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class FocusLogicTest {
-
- @Test
- public void testShouldConsume() {
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_UP));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_DOWN));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_HOME));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
- assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
- }
-
- @Test
- public void testCreateSparseMatrix() {
- // Either, 1) create a helper method to generate/instantiate all possible cell layout that
- // may get created in real world to test this method. OR 2) Move all the matrix
- // management routine to celllayout and write tests for them.
- }
-
- @Test
- public void testMoveFromBottomRightToBottomLeft() {
- int[][] map = transpose(new int[][] {
- {-1, 0, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1, -1},
- {100, 1, -1, -1, -1, -1},
- });
- int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 100, 1, 2, false);
- assertEquals(1, i);
- }
-
- @Test
- public void testMoveFromBottomRightToTopLeft() {
- int[][] map = transpose(new int[][] {
- {-1, 0, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1, -1},
- {100, -1, -1, -1, -1, -1},
- });
- int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 100, 1, 2, false);
- assertEquals(FocusLogic.NEXT_PAGE_FIRST_ITEM, i);
- }
-
- @Test
- public void testMoveIntoHotseatWithEqualHotseatAndWorkspaceColumns() {
- // Test going from an icon right above the All Apps button to the All Apps button.
- int[][] map = transpose(new int[][] {
- {-1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1},
- {-1, -1, 0, -1, -1},
- { 2, 3, 1, 4, 5},
- });
- int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test going from an icon above and to the right of the All Apps
- // button to an icon to the right of the All Apps button.
- map = transpose(new int[][] {
- {-1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1},
- {-1, -1, -1, -1, -1},
- {-1, -1, -1, 0, -1},
- { 2, 3, 1, 4, 5},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
- assertEquals(4, i);
- }
-
- @Test
- public void testMoveIntoHotseatWithExtraColumnForAllApps() {
- // Test going from an icon above and to the left
- // of the All Apps button to the All Apps button.
- int[][] map = transpose(new int[][] {
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, 0,-11, -1, -1, -1},
- {-1, -1, -1, 1, 1, -1, -1},
- });
- int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test going from an icon above and to the right
- // of the All Apps button to the All Apps button.
- map = transpose(new int[][] {
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, 0, -1, -1},
- {-1, -1, -1, 1, -1, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test going from the All Apps button to an icon
- // above and to the right of the All Apps button.
- map = transpose(new int[][] {
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, 0, -1, -1},
- {-1, -1, -1, 1, -1, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_UP, map, 1, 1, 1, true);
- assertEquals(0, i);
- // Test going from an icon above and to the left of the
- // All Apps button in landscape to the All Apps button.
- map = transpose(new int[][] {
- { -1, -1, -1, -1, -1},
- { -1, -1, -1, 0, -1},
- {-11,-11,-11,-11, 1},
- { -1, -1, -1, -1, -1},
- { -1, -1, -1, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test going from the All Apps button in landscape to
- // an icon above and to the left of the All Apps button.
- map = transpose(new int[][] {
- { -1, -1, -1, -1, -1},
- { -1, -1, -1, 0, -1},
- {-11,-11,-11,-11, 1},
- { -1, -1, -1, -1, -1},
- { -1, -1, -1, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, map, 1, 1, 1, true);
- assertEquals(0, i);
- // Test that going to the hotseat always goes to the same row as the original icon.
- map = transpose(new int[][]{
- { 0, 1, 2,-11, 3, 4, 5},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- {-1, -1, -1,-11, -1, -1, -1},
- { 7, 8, 9, 6, 10, 11, 12},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
- assertEquals(7, i);
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 1, 1, 1, true);
- assertEquals(8, i);
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 2, 1, 1, true);
- assertEquals(9, i);
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 3, 1, 1, true);
- assertEquals(10, i);
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 4, 1, 1, true);
- assertEquals(11, i);
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 5, 1, 1, true);
- assertEquals(12, i);
- }
-
- @Test
- public void testCrossingAllAppsColumn() {
- // Test crossing from left to right in portrait.
- int[][] map = transpose(new int[][] {
- {-1, -1,-11, -1, -1},
- {-1, 0,-11, -1, -1},
- {-1, -1,-11, 1, -1},
- {-1, -1,-11, -1, -1},
- {-1, -1, 2, -1, -1},
- });
- int i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test crossing from right to left in portrait.
- map = transpose(new int[][] {
- {-1, -1,-11, -1, -1},
- {-1, -1,-11, 0, -1},
- {-1, 1,-11, -1, -1},
- {-1, -1,-11, -1, -1},
- {-1, -1, 2, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test crossing from left to right in landscape.
- map = transpose(new int[][] {
- { -1, -1, -1, -1, -1},
- { -1, -1, -1, 0, -1},
- {-11,-11,-11,-11, 2},
- { -1, 1, -1, -1, -1},
- { -1, -1, -1, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test crossing from right to left in landscape.
- map = transpose(new int[][] {
- { -1, -1, -1, -1, -1},
- { -1, 0, -1, -1, -1},
- {-11,-11,-11,-11, 2},
- { -1, -1, 1, -1, -1},
- { -1, -1, -1, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 0, 1, 1, true);
- assertEquals(1, i);
- // Test NOT crossing it, if the All Apps button is the only suitable candidate.
- map = transpose(new int[][]{
- {-1, 0, -1, -1, -1},
- {-1, 1, -1, -1, -1},
- {-11, -11, -11, -11, 4},
- {-1, 2, -1, -1, -1},
- {-1, 3, -1, -1, -1},
- });
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 1, 1, 1, true);
- assertEquals(4, i);
- i = FocusLogic.handleKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, map, 2, 1, 1, true);
- assertEquals(4, i);
- }
-
- /** Transposes the matrix so that we can write it in human-readable format in the tests. */
- private int[][] transpose(int[][] m) {
- int[][] t = new int[m[0].length][m.length];
- for (int i = 0; i < m.length; i++) {
- for (int j = 0; j < m[0].length; j++) {
- t[j][i] = m[i][j];
- }
- }
- return t;
- }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java
new file mode 100644
index 0000000..02f8183
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromHome.java
@@ -0,0 +1,142 @@
+/*
+ * 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.tapl;
+
+import android.support.annotation.NonNull;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+
+/**
+ * Operations on AllApps opened from Home.
+ */
+public final class AllAppsFromHome {
+ private static final int MAX_SCROLL_ATTEMPTS = 40;
+ private static final int MIN_INTERACT_SIZE = 100;
+ private static final int FLING_SPEED = 12000;
+
+ private final Launcher mLauncher;
+ private final int mHeight;
+
+ AllAppsFromHome(Launcher launcher) {
+ mLauncher = launcher;
+ final UiObject2 allAppsContainer = assertState();
+ mHeight = allAppsContainer.getVisibleBounds().height();
+ }
+
+ /**
+ * Asserts that we are in all apps.
+ *
+ * @return All apps container.
+ */
+ @NonNull
+ private UiObject2 assertState() {
+ return mLauncher.assertState(Launcher.State.ALL_APPS);
+ }
+
+ /**
+ * Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make
+ * sure the icon is visible.
+ *
+ * @param appName name of the app.
+ * @return The app.
+ */
+ @NonNull
+ public AppIcon getAppIcon(String appName) {
+ final UiObject2 allAppsContainer = assertState();
+ final BySelector appIconSelector = AppIcon.getAppIconSelector(appName);
+ if (!allAppsContainer.hasObject(appIconSelector)) {
+ scrollBackToBeginning();
+ int attempts = 0;
+ while (!allAppsContainer.hasObject(appIconSelector) &&
+ allAppsContainer.scroll(Direction.DOWN, 0.8f)) {
+ mLauncher.assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+ ++attempts <= MAX_SCROLL_ATTEMPTS);
+ assertState();
+ }
+ }
+ assertState();
+
+ final UiObject2 appIcon = mLauncher.getObjectInContainer(allAppsContainer, appIconSelector);
+ ensureIconVisible(appIcon, allAppsContainer);
+ return new AppIcon(mLauncher, appIcon);
+ }
+
+ private void scrollBackToBeginning() {
+ final UiObject2 allAppsContainer = assertState();
+
+ int attempts = 0;
+ allAppsContainer.setGestureMargins(5, 500, 5, 5);
+
+ while (allAppsContainer.scroll(Direction.UP, 0.5f)) {
+ mLauncher.waitForIdle();
+ assertState();
+
+ mLauncher.assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
+ ++attempts <= MAX_SCROLL_ATTEMPTS);
+ }
+
+ mLauncher.waitForIdle();
+ assertState();
+ }
+
+ private void ensureIconVisible(UiObject2 appIcon, UiObject2 allAppsContainer) {
+ final int appHeight = appIcon.getVisibleBounds().height();
+ if (appHeight < MIN_INTERACT_SIZE) {
+ // Try to figure out how much percentage of the container needs to be scrolled in order
+ // to reveal the app icon to have the MIN_INTERACT_SIZE
+ final float pct = Math.max(((float) (MIN_INTERACT_SIZE - appHeight)) / mHeight, 0.2f);
+ allAppsContainer.scroll(Direction.DOWN, pct);
+ mLauncher.waitForIdle();
+ assertState();
+ }
+ }
+
+ /**
+ * Flings forward (down) and waits the fling's end.
+ */
+ public void flingForward() {
+ final UiObject2 allAppsContainer = assertState();
+ // Start the gesture in the center to avoid starting at elements near the top.
+ allAppsContainer.setGestureMargins(0, 0, 0, mHeight / 2);
+ allAppsContainer.fling(Direction.DOWN, FLING_SPEED);
+ assertState();
+ }
+
+ /**
+ * Flings backward (up) and waits the fling's end.
+ */
+ public void flingBackward() {
+ final UiObject2 allAppsContainer = assertState();
+ // Start the gesture in the center, for symmetry with forward.
+ allAppsContainer.setGestureMargins(0, mHeight / 2, 0, 0);
+ allAppsContainer.fling(Direction.UP, FLING_SPEED);
+ assertState();
+ }
+
+ /**
+ * Gets the UI object for AllApps.
+ * Used by NexusLauncherStrategy.openAllApps(). No one else should use it.
+ *
+ * @return container object.
+ */
+ @Deprecated
+ @NonNull
+ public UiObject2 getObjectDeprecated() {
+ return assertState();
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
new file mode 100644
index 0000000..cba7086
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -0,0 +1,63 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Point;
+import android.support.annotation.NonNull;
+import android.support.test.uiautomator.UiObject2;
+
+/**
+ * Operations on AllApps opened from Overview.
+ * Scroll gestures that are OK for {@link AllAppsFromHome} may close it, so they are not supported.
+ */
+public final class AllAppsFromOverview {
+ private final Launcher mLauncher;
+
+ AllAppsFromOverview(Launcher launcher) {
+ mLauncher = launcher;
+ assertState();
+ }
+
+ /**
+ * Asserts that we are in all apps.
+ *
+ * @return All apps container.
+ */
+ @NonNull
+ private UiObject2 assertState() {
+ return mLauncher.assertState(Launcher.State.ALL_APPS);
+ }
+
+ /**
+ * Swipes down to switch back to Overview whence we came from.
+ *
+ * @return the overview panel.
+ */
+ @NonNull
+ public Overview switchBackToOverview() {
+ final UiObject2 allAppsContainer = assertState();
+ // Swipe from the search box to the bottom.
+ final UiObject2 qsb = mLauncher.waitForObjectInContainer(
+ allAppsContainer, "search_container_all_apps");
+ final Point start = qsb.getVisibleCenter();
+ final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.6);
+ mLauncher.swipe(start.x, start.y, start.x, endY, (endY - start.y) / 100); // 100 px/step
+
+ return new Overview(mLauncher);
+ }
+
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
new file mode 100644
index 0000000..73a74f2
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -0,0 +1,53 @@
+/*
+ * 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.tapl;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.widget.TextView;
+
+/**
+ * App icon, whether in all apps or in workspace/
+ */
+public final class AppIcon {
+ private final Launcher mLauncher;
+ private final UiObject2 mIcon;
+
+ AppIcon(Launcher launcher, UiObject2 icon) {
+ mLauncher = launcher;
+ mIcon = icon;
+ }
+
+ static BySelector getAppIconSelector(String appName) {
+ return By.clazz(TextView.class).text(appName).pkg(Launcher.LAUNCHER_PKG);
+ }
+
+ /**
+ * Clicks the icon to launch its app.
+ */
+ public void launch() {
+ mLauncher.assertTrue("Launching an app didn't open a new window: " + mIcon.getText(),
+ mIcon.clickAndWait(Until.newWindow(), Launcher.APP_LAUNCH_TIMEOUT_MS));
+ mLauncher.assertState(Launcher.State.BACKGROUND);
+ }
+
+ UiObject2 getIcon() {
+ return mIcon;
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
new file mode 100644
index 0000000..0ec1a64
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -0,0 +1,187 @@
+/*
+ * 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.tapl;
+
+import static junit.framework.TestCase.assertTrue;
+
+import android.graphics.Point;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+
+/**
+ * Operations on the home screen.
+ */
+public final class Home {
+
+ private final Launcher mLauncher;
+ private final UiObject2 mHotseat;
+ private final int ICON_DRAG_SPEED = 2000;
+
+ Home(Launcher launcher) {
+ mLauncher = launcher;
+ assertState();
+ mHotseat = launcher.waitForLauncherObject("hotseat");
+ }
+
+ /**
+ * Asserts that we are in home.
+ *
+ * @return Workspace.
+ */
+ @NonNull
+ private UiObject2 assertState() {
+ return mLauncher.assertState(Launcher.State.HOME);
+ }
+
+ /**
+ * Swipes up or presses the square button to switch to Overview.
+ *
+ * @return the Overview panel object.
+ */
+ @NonNull
+ public Overview switchToOverview() {
+ assertState();
+ if (mLauncher.isSwipeUpEnabled()) {
+ final int height = mLauncher.getDevice().getDisplayHeight();
+ final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
+
+ // Swipe from nav bar to 2/3rd down the screen.
+ mLauncher.swipe(
+ navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
+ navBar.getVisibleBounds().centerX(), height * 2 / 3,
+ (navBar.getVisibleBounds().centerY() - height * 2 / 3) / 100); // 100 px/step
+ } else {
+ mLauncher.getSystemUiObject("recent_apps").click();
+ }
+
+ return new Overview(mLauncher);
+ }
+
+ /**
+ * Swipes up to All Apps.
+ *
+ * @return the App Apps object.
+ */
+ @NonNull
+ public AllAppsFromHome switchToAllApps() {
+ assertState();
+ if (mLauncher.isSwipeUpEnabled()) {
+ int midX = mLauncher.getDevice().getDisplayWidth() / 2;
+ int height = mLauncher.getDevice().getDisplayHeight();
+ // Swipe from 6/7ths down the screen to 1/7th down the screen.
+ mLauncher.swipe(
+ midX,
+ height * 6 / 7,
+ midX,
+ height / 7,
+ (height * 2 / 3) / 100); // 100 px/step
+ } else {
+ // Swipe from the hotseat to near the top, e.g. 10% of the screen.
+ final UiObject2 hotseat = mHotseat;
+ final Point start = hotseat.getVisibleCenter();
+ final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
+ mLauncher.swipe(
+ start.x,
+ start.y,
+ start.x,
+ endY,
+ (start.y - endY) / 100); // 100 px/step
+ }
+
+ return new AllAppsFromHome(mLauncher);
+ }
+
+ /**
+ * Returns an icon for the app, if currently visible.
+ *
+ * @param appName name of the app
+ * @return app icon, if found, null otherwise.
+ */
+ @Nullable
+ public AppIcon tryGetWorkspaceAppIcon(String appName) {
+ final UiObject2 workspace = assertState();
+ final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName));
+ return icon != null ? new AppIcon(mLauncher, icon) : null;
+ }
+
+ /**
+ * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
+ * second screen.
+ */
+ public void ensureWorkspaceIsScrollable() {
+ final UiObject2 workspace = assertState();
+ if (!isWorkspaceScrollable(workspace)) {
+ dragIconToNextScreen(getHotseatAppIcon("Messages"), workspace);
+ }
+ assertTrue("Home screen workspace didn't become scrollable",
+ isWorkspaceScrollable(workspace));
+ }
+
+ private boolean isWorkspaceScrollable(UiObject2 workspace) {
+ return workspace.isScrollable();
+ }
+
+ @NonNull
+ private AppIcon getHotseatAppIcon(String appName) {
+ return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
+ mHotseat, AppIcon.getAppIconSelector(appName)));
+ }
+
+ private void dragIconToNextScreen(AppIcon app, UiObject2 workspace) {
+ final Point dest = new Point(
+ mLauncher.getDevice().getDisplayWidth(), workspace.getVisibleBounds().centerY());
+ app.getIcon().drag(dest, ICON_DRAG_SPEED);
+ assertState();
+ }
+
+ /**
+ * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
+ * recoil to complete.
+ */
+ public void flingForward() {
+ final UiObject2 workspace = assertState();
+ workspace.fling(Direction.RIGHT);
+ mLauncher.waitForIdle();
+ assertState();
+ }
+
+ /**
+ * Flings to get to screens on the left. Waits for scrolling and a possible overscroll
+ * recoil to complete.
+ */
+ public void flingBackward() {
+ final UiObject2 workspace = assertState();
+ workspace.fling(Direction.LEFT);
+ mLauncher.waitForIdle();
+ assertState();
+ }
+
+ /**
+ * Opens widgets container by pressing Ctrl+W.
+ *
+ * @return the widgets container.
+ */
+ @NonNull
+ public Widgets openAllWidgets() {
+ assertState();
+ mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
+ return new Widgets(mLauncher);
+ }
+}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launcher.java b/tests/tapl/com/android/launcher3/tapl/Launcher.java
new file mode 100644
index 0000000..5201dc8
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Launcher.java
@@ -0,0 +1,303 @@
+/*
+ * 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.tapl;
+
+import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
+
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * The main tapl object. The only object that can be explicitly constructed by the using code. It
+ * produces all other objects.
+ */
+public final class Launcher {
+
+ private static final String WORKSPACE_RES_ID = "workspace";
+ private static final String APPS_RES_ID = "apps_view";
+ private static final String OVERVIEW_RES_ID = "overview_panel";
+ private static final String WIDGETS_RES_ID = "widgets_list_view";
+
+ enum State {HOME, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND}
+
+ static final String LAUNCHER_PKG = "com.google.android.apps.nexuslauncher";
+ static final int APP_LAUNCH_TIMEOUT_MS = 10000;
+ private static final int UI_OBJECT_WAIT_TIMEOUT_MS = 10000;
+ private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
+ "config_swipe_up_gesture_setting_available";
+ private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
+ "config_swipe_up_gesture_default";
+ private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+ private static final String TAG = "tapl.Launcher";
+ private final UiDevice mDevice;
+ private final boolean mSwipeUpEnabled;
+
+ /**
+ * Constructs the root of TAPL hierarchy. You get all other object from it.
+ */
+ public Launcher(UiDevice device) {
+ mDevice = device;
+ final boolean swipeUpEnabledDefault =
+ !getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME) ||
+ getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
+ mSwipeUpEnabled = Settings.Secure.getInt(
+ InstrumentationRegistry.getTargetContext().getContentResolver(),
+ SWIPE_UP_SETTING_NAME,
+ swipeUpEnabledDefault ? 1 : 0) == 1;
+ }
+
+ private boolean getSystemBooleanRes(String resName) {
+ final Resources res = Resources.getSystem();
+ final int resId = res.getIdentifier(resName, "bool", "android");
+ assertTrue("Resource not found: " + resName, resId != 0);
+ return res.getBoolean(resId);
+ }
+
+ private void dumpViewHierarchy() {
+ final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ mDevice.dumpWindowHierarchy(stream);
+ stream.flush();
+ stream.close();
+ for (String line : stream.toString().split("\\r?\\n")) {
+ Log.e(TAG, line.trim());
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "error dumping XML to logcat", e);
+ }
+ }
+
+ void fail(String message) {
+ dumpViewHierarchy();
+ Assert.fail(message);
+ }
+
+ void assertTrue(String message, boolean condition) {
+ if (!condition) {
+ fail(message);
+ }
+ }
+
+ void assertNotNull(String message, Object object) {
+ assertTrue(message, object != null);
+ }
+
+ private void failEquals(String message, Object actual) {
+ String formatted = "Values should be different. ";
+ if (message != null) {
+ formatted = message + ". ";
+ }
+
+ formatted += "Actual: " + actual;
+ fail(formatted);
+ }
+
+ void assertNotEquals(String message, int unexpected, int actual) {
+ if (unexpected == actual) {
+ failEquals(message, actual);
+ }
+ }
+
+ boolean isSwipeUpEnabled() {
+ return mSwipeUpEnabled;
+ }
+
+ UiObject2 assertState(State state) {
+ switch (state) {
+ case HOME: {
+ //waitUntilGone(APPS_RES_ID);
+ waitUntilGone(OVERVIEW_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return waitForLauncherObject(WORKSPACE_RES_ID);
+ }
+ case WIDGETS: {
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(APPS_RES_ID);
+ waitUntilGone(OVERVIEW_RES_ID);
+ return waitForLauncherObject(WIDGETS_RES_ID);
+ }
+ case ALL_APPS: {
+ waitUntilGone(OVERVIEW_RES_ID);
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return waitForLauncherObject(APPS_RES_ID);
+ }
+ case OVERVIEW: {
+ //waitForLauncherObject(APPS_RES_ID);
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return waitForLauncherObject(OVERVIEW_RES_ID);
+ }
+ case BACKGROUND: {
+ waitUntilGone(WORKSPACE_RES_ID);
+ waitUntilGone(APPS_RES_ID);
+ waitUntilGone(OVERVIEW_RES_ID);
+ waitUntilGone(WIDGETS_RES_ID);
+ return null;
+ }
+ default:
+ fail("Invalid state: " + state);
+ return null;
+ }
+ }
+
+ /**
+ * Presses nav bar home button.
+ *
+ * @return the Home object.
+ */
+ public Home pressHome() {
+ getSystemUiObject("home").click();
+ return getHome();
+ }
+
+ /**
+ * Gets the Home object if the current state is "active home", i.e. workspace. Fails if the
+ * launcher is not in that state.
+ *
+ * @return Home object.
+ */
+ @NonNull
+ public Home getHome() {
+ return new Home(this);
+ }
+
+ /**
+ * Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
+ * not in that state.
+ *
+ * @return Widgets object.
+ */
+ @NonNull
+ public Widgets getAllWidgets() {
+ return new Widgets(this);
+ }
+
+ /**
+ * Gets the Overview object if the current state is showing the overview panel. Fails if the
+ * launcher is not in that state.
+ *
+ * @return Overview object.
+ */
+ @NonNull
+ public Overview getOverview() {
+ return new Overview(this);
+ }
+
+ /**
+ * Gets the All Apps object if the current state is showing the all apps panel. Fails if the
+ * launcher is not in that state.
+ *
+ * @return All Aps object.
+ */
+ @NonNull
+ public AllAppsFromHome getAllApps() {
+ return new AllAppsFromHome(this);
+ }
+
+ /**
+ * Gets the All Apps object if the current state is showing the all apps panel. Returns null if
+ * the launcher is not in that state.
+ *
+ * @return All Aps object or null.
+ */
+ @Nullable
+ public AllAppsFromHome tryGetAllApps() {
+ return tryGetLauncherObject(APPS_RES_ID) != null ? getAllApps() : null;
+ }
+
+ private void waitUntilGone(String resId) {
+// assertTrue("Unexpected launcher object visible: " + resId,
+// mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
+// UI_OBJECT_WAIT_TIMEOUT_MS));
+ }
+
+ @NonNull
+ UiObject2 getSystemUiObject(String resId) {
+ try {
+ mDevice.wakeUp();
+ } catch (RemoteException e) {
+ fail("Failed to wake up the device: " + e);
+ }
+ final UiObject2 object = mDevice.findObject(By.res(SYSTEMUI_PACKAGE, resId));
+ assertNotNull("Can't find a systemui object with id: " + resId, object);
+ return object;
+ }
+
+ @NonNull
+ UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
+ final UiObject2 object = container.findObject(selector);
+ assertNotNull("Can't find an object with selector: " + selector, object);
+ return object;
+ }
+
+ @Nullable
+ private UiObject2 tryGetLauncherObject(String resName) {
+ return mDevice.findObject(getLauncherObjectSelector(resName));
+ }
+
+ @NonNull
+ UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
+ final UiObject2 object = container.wait(
+ Until.findObject(getLauncherObjectSelector(resName)),
+ UI_OBJECT_WAIT_TIMEOUT_MS);
+ assertNotNull("Can find a launcher object id: " + resName + " in container: " +
+ container.getResourceName(), object);
+ return object;
+ }
+
+ @NonNull
+ UiObject2 waitForLauncherObject(String resName) {
+ final UiObject2 object = mDevice.wait(Until.findObject(getLauncherObjectSelector(resName)),
+ UI_OBJECT_WAIT_TIMEOUT_MS);
+ assertNotNull("Can find a launcher object; id: " + resName, object);
+ return object;
+ }
+
+ static BySelector getLauncherObjectSelector(String resName) {
+ return By.res(LAUNCHER_PKG, resName);
+ }
+
+ @NonNull
+ UiDevice getDevice() {
+ return mDevice;
+ }
+
+ void swipe(int startX, int startY, int endX, int endY, int steps) {
+ mDevice.swipe(startX, startY, endX, endY, steps);
+ waitForIdle();
+ }
+
+ void waitForIdle() {
+ mDevice.waitForIdle();
+ }
+}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
new file mode 100644
index 0000000..2251655
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -0,0 +1,111 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Point;
+import android.support.annotation.NonNull;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Overview pane.
+ */
+public final class Overview {
+ private static final int DEFAULT_FLING_SPEED = 15000;
+
+ private final Launcher mLauncher;
+
+ Overview(Launcher launcher) {
+ mLauncher = launcher;
+ assertState();
+ }
+
+ /**
+ * Asserts that we are in overview.
+ *
+ * @return Overview panel.
+ */
+ @NonNull
+ private UiObject2 assertState() {
+ return mLauncher.assertState(Launcher.State.OVERVIEW);
+ }
+
+ /**
+ * Flings forward (left) and waits the fling's end.
+ */
+ public void flingForward() {
+ final UiObject2 overview = assertState();
+ overview.fling(Direction.LEFT, DEFAULT_FLING_SPEED);
+ mLauncher.waitForIdle();
+ assertState();
+ }
+
+ /**
+ * Flings backward (right) and waits the fling's end.
+ */
+ public void flingBackward() {
+ final UiObject2 overview = assertState();
+ overview.fling(Direction.RIGHT, DEFAULT_FLING_SPEED);
+ mLauncher.waitForIdle();
+ assertState();
+ }
+
+ /**
+ * Gets the current task in the carousel, or fails if the carousel is empty.
+ *
+ * @return the task in the middle of the visible tasks list.
+ */
+ @NonNull
+ public OverviewTask getCurrentTask() {
+ assertState();
+ final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
+ Launcher.getLauncherObjectSelector("snapshot"));
+ mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+
+ // taskViews contains up to 3 task views: the 'main' (having the widest visible
+ // part) one in the center, and parts of its right and left siblings. Find the
+ // main task view by its width.
+ final UiObject2 widestTask = Collections.max(taskViews,
+ (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
+ t2.getVisibleBounds().width()));
+
+ return new OverviewTask(mLauncher, widestTask);
+ }
+
+ /**
+ * Swipes up to All Apps.
+ *
+ * @return the App Apps object.
+ */
+ @NonNull
+ public AllAppsFromOverview switchToAllApps() {
+ assertState();
+
+ // Swipe from the hotseat to near the top, e.g. 10% of the screen.
+ final UiObject2 predictionRow = mLauncher.waitForLauncherObject(
+ "prediction_row");
+ final Point start = predictionRow.getVisibleCenter();
+ final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f);
+ mLauncher.swipe(
+ start.x, start.y, start.x, endY, (start.y - endY) / 100); // 100 px/step
+
+ return new AllAppsFromOverview(mLauncher);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
new file mode 100644
index 0000000..68d3082
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tapl;
+
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+/**
+ * A recent task in the overview panel carousel.
+ */
+public final class OverviewTask {
+ private final Launcher mLauncher;
+ private final UiObject2 mTask;
+
+ OverviewTask(Launcher launcher, UiObject2 task) {
+ mLauncher = launcher;
+ assertState();
+ mTask = task;
+ }
+
+ /**
+ * Asserts that we are in overview.
+ *
+ * @return Overview panel.
+ */
+ private void assertState() {
+ mLauncher.assertState(Launcher.State.OVERVIEW);
+ }
+
+ /**
+ * Swipes the task up.
+ */
+ public void dismiss() {
+ assertState();
+ // Dismiss the task via flinging it up.
+ mTask.fling(Direction.DOWN);
+ mLauncher.waitForIdle();
+ }
+
+ /**
+ * Clicks at the task.
+ */
+ public void open() {
+ assertState();
+ mLauncher.assertTrue("Launching task didn't open a new window: " +
+ mTask.getParent().getContentDescription(),
+ mTask.clickAndWait(Until.newWindow(), Launcher.APP_LAUNCH_TIMEOUT_MS));
+ mLauncher.assertState(Launcher.State.BACKGROUND);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
new file mode 100644
index 0000000..7a5198a
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tapl;
+
+import android.support.annotation.NonNull;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+
+/**
+ * All widgets container.
+ */
+public final class Widgets {
+ private static final int FLING_SPEED = 12000;
+
+ private final Launcher mLauncher;
+
+ Widgets(Launcher launcher) {
+ mLauncher = launcher;
+ assertState();
+ }
+
+ /**
+ * Flings forward (down) and waits the fling's end.
+ */
+ public void flingForward() {
+ final UiObject2 widgetsContainer = assertState();
+ widgetsContainer.fling(Direction.DOWN, FLING_SPEED);
+ mLauncher.waitForIdle();
+ assertState();
+ }
+
+ /**
+ * Flings backward (up) and waits the fling's end.
+ */
+ public void flingBackward() {
+ final UiObject2 widgetsContainer = assertState();
+ widgetsContainer.fling(Direction.UP, FLING_SPEED);
+ mLauncher.waitForIdle();
+ assertState();
+ }
+
+ /**
+ * Asserts that we are in widgets.
+ *
+ * @return Widgets container.
+ */
+ @NonNull
+ private UiObject2 assertState() {
+ return mLauncher.assertState(Launcher.State.WIDGETS);
+ }
+}