Merge branch 'ub-launcher3-edmonton-polish' into pi-dev

Bug: 110425849
Bug: 80134723
Test: manual
Change-Id: I0cf89a4ee08ff2a36e5a586f7e0c04ae970bf13c
This commit is contained in:
Jon
2018-06-21 21:14:05 -04:00
104 changed files with 2887 additions and 1508 deletions
+69 -96
View File
@@ -29,41 +29,52 @@ LOCAL_SDK_VERSION := current
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
@@ -81,101 +92,78 @@ include $(BUILD_PACKAGE)
# 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
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
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_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/AndroidManifest.xml \
$(LOCAL_PATH)/AndroidManifest-common.xml
@@ -185,47 +173,33 @@ LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
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
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(LOCAL_PATH)/go/AndroidManifest.xml \
@@ -234,7 +208,6 @@ LOCAL_FULL_LIBS_MANIFEST_FILES := \
LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
include $(BUILD_PACKAGE)
+1 -1
View File
@@ -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" >
+1 -1
View File
@@ -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" >
+1 -1
View File
@@ -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" >
Binary file not shown.
+10 -27
View File
@@ -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>
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>
+38
View File
@@ -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>
@@ -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>
@@ -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"
/>
android:translationY="@dimen/task_thumbnail_half_top_margin"
/>
+5 -19
View File
@@ -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>
android:clipToPadding="false"
android:accessibilityPaneTitle="@string/accessibility_recent_apps"
android:visibility="invisible" />
+5 -4
View File
@@ -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>
+25 -15
View File
@@ -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>
@@ -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>
+19
View File
@@ -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>
+28
View File
@@ -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>
+7 -5
View File
@@ -17,8 +17,8 @@
<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="quickscrub_adjacent_visible_width">20dp</dimen>
@@ -32,7 +32,7 @@
<!-- Launcher app transition -->
<dimen name="content_trans_y">50dp</dimen>
<dimen name="workspace_trans_y">50dp</dimen>
<dimen name="springs_trans_y">-70dp</dimen>
<dimen name="closing_window_trans_y">115dp</dimen>
<dimen name="recents_empty_message_text_size">16sp</dimen>
@@ -41,6 +41,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 +51,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>
+28
View File
@@ -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>
@@ -16,8 +16,11 @@
package com.android.launcher3;
import static android.view.View.TRANSLATION_Y;
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;
@@ -25,14 +28,14 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OSCILLATE;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
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 +56,8 @@ import android.os.Build;
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.util.Property;
import android.view.View;
import android.view.ViewGroup;
@@ -72,6 +74,7 @@ import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
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 +83,8 @@ import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
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 +96,16 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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";
@@ -107,12 +119,20 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
public static final int RECENTS_LAUNCH_DURATION = 336;
public static final int RECENTS_QUICKSCRUB_LAUNCH_DURATION = 300;
private static final int LAUNCHER_RESUME_START_DELAY = 100;
private static final int LAUNCHER_RESUME_START_DELAY = 40;
private static final int CLOSING_TRANSITION_DURATION_MS = 250;
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
private static final int APP_CLOSE_ROW_START_DELAY_MS = 8;
// The sum of [slide, oscillate, and settle] should be <= LAUNCHER_RESUME_TOTAL_DURATION.
private static final int LAUNCHER_RESUME_TOTAL_DURATION = 346;
private static final int SPRING_SLIDE_DURATION = 166;
private static final int SPRING_OSCILLATE_DURATION = 130;
private static final int SPRING_SETTLE_DURATION = 50;
private final Launcher mLauncher;
private final DragLayer mDragLayer;
private final AlphaProperty mDragLayerAlpha;
@@ -121,7 +141,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
private final boolean mIsRtl;
private final float mContentTransY;
private final float mWorkspaceTransY;
private final float mStartSlideTransY;
private final float mEndSlideTransY;
private final float mClosingWindowTransY;
private DeviceProfile mDeviceProfile;
@@ -151,8 +172,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
Resources res = mLauncher.getResources();
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
mStartSlideTransY = res.getDimensionPixelSize(R.dimen.springs_trans_y);
mEndSlideTransY = -mStartSlideTransY * 0.1f;
mLauncher.addOnDeviceProfileChangeListener(this);
registerRemoteAnimations();
@@ -187,7 +209,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +232,14 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
};
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 +396,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +408,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +434,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
/**
* @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 +491,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
v.setVisibility(View.INVISIBLE);
AnimatorSet appIconAnimatorSet = new AnimatorSet();
int[] dragLayerBounds = new int[2];
mDragLayer.getLocationOnScreen(dragLayerBounds);
@@ -500,8 +520,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
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 +532,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
.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 +545,9 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +555,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
}
});
return appIconAnimatorSet;
}
/**
@@ -558,22 +577,21 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +602,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +615,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +625,27 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +689,11 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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 +730,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
* 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 +741,28 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
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);
}
});
@@ -761,25 +786,33 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
});
} else {
AnimatorSet workspaceAnimator = new AnimatorSet();
mDragLayer.setTranslationY(-mWorkspaceTransY);;
workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
-mWorkspaceTransY, 0));
mDragLayerAlpha.setValue(0);
workspaceAnimator.play(ObjectAnimator.ofFloat(
mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
workspaceAnimator.setDuration(333);
workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
ShortcutAndWidgetContainer currentPage = ((CellLayout) mLauncher.getWorkspace()
.getChildAt(mLauncher.getWorkspace().getCurrentPage()))
.getShortcutsAndWidgets();
// Set up springs on workspace items.
for (int i = currentPage.getChildCount() - 1; i >= 0; i--) {
View child = currentPage.getChildAt(i);
CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
addStaggeredAnimationForView(child, workspaceAnimator, lp.cellY + lp.cellVSpan);
}
// Set up a spring for the shelf.
if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
float shiftRange = allAppsController.getShiftRange();
float slideStart = shiftRange / (shiftRange - mStartSlideTransY);
float oscillateStart = shiftRange / (shiftRange - mEndSlideTransY);
buildSpringAnimation(workspaceAnimator, allAppsController, ALL_APPS_PROGRESS,
0 /* startDelay */, slideStart, oscillateStart, 1f /* finalPosition */);
}
mDragLayer.getScrim().hideSysUiScrim(true);
// Pause page indicator animations as they lead to layer trashing.
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
workspaceAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -790,6 +823,66 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
}
/**
* Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row.
*
* @param v View in a ShortcutAndWidgetContainer.
* @param row The bottom-most row that contains the view.
*/
private void addStaggeredAnimationForView(View v, AnimatorSet outAnimator, int row) {
// Invert the rows, because we stagger starting from the bottom of the screen.
int invertedRow = LauncherAppState.getIDP(mLauncher).numRows - row + 1;
long startDelay = (long) (invertedRow * APP_CLOSE_ROW_START_DELAY_MS);
v.setAlpha(0);
ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 1f);
alpha.setInterpolator(LINEAR);
alpha.setDuration(SPRING_SLIDE_DURATION + SPRING_OSCILLATE_DURATION);
alpha.setStartDelay(startDelay);
outAnimator.play(alpha);
buildSpringAnimation(outAnimator, v, TRANSLATION_Y, startDelay, mStartSlideTransY,
mEndSlideTransY, 0f /* finalPosition */);
outAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
v.setAlpha(1f);
v.setTranslationY(0);
}
});
}
/**
* Spring animations consists of three sequential animators: a slide, an oscillation, and
* a settle.
*/
private <T> void buildSpringAnimation(AnimatorSet outAnimator, T objectToSpring,
Property<T, Float> property, long startDelay, float slideStart, float oscillateStart,
float finalPosition) {
// Ensures a clean hand-off between slide and oscillate.
float slideEnd = Utilities.mapToRange(0, 0, 1f, oscillateStart, finalPosition, OSCILLATE);
property.set(objectToSpring, slideStart);
ObjectAnimator slideIn = ObjectAnimator.ofFloat(objectToSpring, property, slideStart,
slideEnd);
slideIn.setInterpolator(DEACCEL);
slideIn.setStartDelay(startDelay);
slideIn.setDuration(SPRING_SLIDE_DURATION);
ObjectAnimator oscillate = ObjectAnimator.ofFloat(objectToSpring, property, oscillateStart,
finalPosition);
oscillate.setInterpolator(OSCILLATE);
oscillate.setDuration(SPRING_OSCILLATE_DURATION);
ObjectAnimator settle = ObjectAnimator.ofFloat(objectToSpring, property, finalPosition);
settle.setInterpolator(LINEAR);
settle.setDuration(SPRING_SETTLE_DURATION);
outAnimator.playSequentially(slideIn, oscillate, settle);
}
private void resetContentView() {
mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
mDragLayerAlpha.setValue(1f);
@@ -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 @@ public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler
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);
}
}
@@ -25,9 +25,11 @@ import com.android.launcher3.AbstractFloatingView;
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 class OverviewState extends LauncherState {
public void onStateDisabled(Launcher launcher) {
RecentsView rv = launcher.getOverviewPanel();
rv.setOverviewStateEnabled(false);
RecentsModel.getInstance(launcher).resetAssistCache();
}
@Override
@@ -118,6 +121,11 @@ public class OverviewState extends LauncherState {
/ 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;
@@ -18,20 +18,24 @@ package com.android.launcher3.uioverrides;
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
// 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);
}
@@ -25,7 +25,7 @@ import static com.android.launcher3.anim.Interpolators.LINEAR;
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.AnimatorSetBuilder;
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 @@ public class RecentsViewStateController implements StateHandler {
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) {
@@ -115,8 +115,8 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
} 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 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
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) {
@@ -25,7 +25,6 @@ import static com.android.launcher3.LauncherState.NORMAL;
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.anim.AnimatorPlaybackController;
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;
@@ -171,7 +169,8 @@ public class UiFactory {
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) {
@@ -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.LauncherState;
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 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
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,65 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
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());
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 +448,7 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
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 +466,7 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
return (transitionLength, interactionType) -> { };
}
RecentsViewContainer rv = activity.getOverviewPanelContainer();
RecentsView rv = activity.getOverviewPanel();
rv.setContentAlpha(0);
return new AnimationFactory() {
@@ -451,8 +490,7 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
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);
@@ -20,18 +20,18 @@ import static com.android.launcher3.LauncherState.ALL_APPS;
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 android.animation.ValueAnimator;
import android.view.Surface;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
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.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 +39,6 @@ import com.android.launcher3.util.FlingBlockCheck;
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 +64,16 @@ public class LongSwipeHelper {
}
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,8 +83,7 @@ public class LongSwipeHelper {
}
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);
}
@@ -136,31 +135,6 @@ public class LongSwipeHelper {
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 +150,12 @@ public class LongSwipeHelper {
callback.run();
}
public float getTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
if (!(app.isNotInRecents
|| app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
return 0;
}
return expectedAlpha;
}
}
@@ -78,6 +78,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
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 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
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 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
mBackgroundThreadChoreographer = backgroundThreadChoreographer;
mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget);
mOverviewCallbacks = overviewCallbacks;
mTaskOverlayFactory = taskOverlayFactory;
}
@Override
@@ -233,14 +235,22 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
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();
@@ -67,6 +67,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
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 @@ public class OverviewCommandHelper {
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
@@ -98,7 +98,7 @@ public class QuickScrubController implements OnAlarmListener {
}
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 @@ public class QuickScrubController implements OnAlarmListener {
} 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 @@ public class QuickScrubController implements OnAlarmListener {
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 @@ public class QuickScrubController implements OnAlarmListener {
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);
@@ -20,6 +20,7 @@ import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
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.launcher3.views.BaseDragLayer;
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 @@ public class RecentsActivity extends BaseDraggingActivity {
private Handler mUiHandler = new Handler(Looper.getMainLooper());
private RecentsRootView mRecentsRootView;
private FallbackRecentsView mFallbackRecentsView;
private RecentsViewContainer mOverviewPanelContainer;
private Configuration mOldConfig;
@@ -84,7 +83,6 @@ public class RecentsActivity extends BaseDraggingActivity {
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 @@ public class RecentsActivity extends BaseDraggingActivity {
return (T) mFallbackRecentsView;
}
public RecentsViewContainer getOverviewPanelContainer() {
return mOverviewPanelContainer;
}
@Override
public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
return null;
@@ -193,7 +187,8 @@ public class RecentsActivity extends BaseDraggingActivity {
};
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));
}
/**
@@ -49,6 +49,9 @@ public class RecentsAnimationWrapper {
this.mController = controller;
this.targetSet = targetSet;
if (controller == null) {
return;
}
if (mInputConsumerEnabled) {
enableInputConsumer();
}
@@ -256,6 +256,10 @@ public class RecentsModel extends TaskStackChangeListener {
}
}
public void resetAssistCache() {
mCachedAssistData.clear();
}
@WorkerThread
public void preloadAssistData(int taskId, Bundle data) {
mMainThreadExecutor.execute(() -> {
@@ -18,6 +18,7 @@ package com.android.quickstep;
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 @@ public class TaskOverlayFactory {
return sInstance;
}
@AnyThread
public boolean needAssist() {
return false;
}
public TaskOverlay createOverlay(View thumbnailView) {
return new TaskOverlay();
}
@@ -152,8 +152,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
};
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) {
@@ -246,6 +245,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
};
taskView.launchTask(true, resultCallback, mHandler);
dismissTaskMenuView(activity);
};
}
}
@@ -265,4 +265,9 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
return null;
}
}
private static void dismissTaskMenuView(BaseDraggingActivity activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
}
}
@@ -46,6 +46,7 @@ import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier;
import java.util.List;
@@ -74,8 +75,11 @@ public class TaskUtils {
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 @@ public class TaskUtils {
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 class TaskUtils {
*/
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 @@ public class TaskUtils {
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 @@ public class TaskUtils {
@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);
@@ -49,6 +49,7 @@ import com.android.quickstep.views.RecentsView;
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 @@ public class TouchInteractionService extends Service {
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewInteractionState mOverviewInteractionState;
private OverviewCallbacks mOverviewCallbacks;
private TaskOverlayFactory mTaskOverlayFactory;
private Choreographer mMainThreadChoreographer;
private Choreographer mBackgroundThreadChoreographer;
@@ -186,6 +188,7 @@ public class TouchInteractionService extends Service {
mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
mOverviewInteractionState = OverviewInteractionState.getInstance(this);
mOverviewCallbacks = OverviewCallbacks.get(this);
mTaskOverlayFactory = TaskOverlayFactory.get(this);
sConnected = true;
@@ -238,7 +241,7 @@ public class TouchInteractionService extends Service {
mOverviewCommandHelper.overviewIntent,
mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
tracker);
mTaskOverlayFactory, tracker);
}
}
@@ -406,6 +409,6 @@ public class TouchInteractionService extends Service {
sRemoteUiThread.start();
}
new Handler(sRemoteUiThread.getLooper()).post(() ->
mBackgroundThreadChoreographer = Choreographer.getInstance());
mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance());
}
}
@@ -16,6 +16,7 @@
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;
@@ -23,6 +24,7 @@ import static com.android.launcher3.anim.Interpolators.LINEAR;
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 +37,7 @@ import android.graphics.Canvas;
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 +46,7 @@ import android.support.annotation.AnyThread;
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 +61,7 @@ import com.android.launcher3.R;
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 +84,12 @@ import com.android.systemui.shared.system.InputConsumerController;
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 +110,21 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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 +148,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
"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 +158,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
"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;
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 +203,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private T mActivity;
private LayoutListener mLayoutListener;
private RecentsView mRecentsView;
private SyncRtSurfaceTransactionApplier mSyncTransactionApplier;
private QuickScrubController mQuickScrubController;
private AnimationFactory mAnimationFactory = (t, i) -> { };
@@ -199,6 +211,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private boolean mWasLauncherAlreadyVisible;
private boolean mPassedOverviewThreshold;
private boolean mGestureStarted;
private int mLogAction = Touch.SWIPE;
private float mCurrentQuickScrubProgress;
@@ -219,6 +232,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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 +260,19 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
};
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 +295,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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 +380,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
// 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);
@@ -546,10 +573,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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 +590,17 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
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 +622,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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 +647,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mRecentsAnimationWrapper.setController(controller, targets);
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mPassedOverviewThreshold = false;
}
public void onRecentsAnimationCanceled() {
@@ -609,7 +659,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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 +676,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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,17 +697,23 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
float velocityPxPerMs = endVelocity / 1000;
long duration = MAX_SWIPE_DURATION;
final float endShift;
final float startShift;
final Interpolator interpolator;
if (!isFling) {
endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted ? 1 : 0;
long expectedDuration = Math.abs(Math.round((endShift - mCurrentShift.value)
* MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
startShift = mCurrentShift.value;
interpolator = DEACCEL;
} else {
endShift = endVelocity < 0 ? 1 : 0;
interpolator = endVelocity < 0
? Interpolators.overshootInterpolatorForVelocity(velocityPxPerMs, 2f)
: DEACCEL;
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
@@ -665,26 +722,30 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
// 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));
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);
startShift = Utilities.boundToRange(mCurrentShift.value - velocityPxPerMs
* SINGLE_FRAME_MS / (mTransitionDragLength), 0, 1);
}
animateToProgress(startShift, endShift, duration, DEACCEL);
animateToProgress(startShift, endShift, duration, interpolator);
}
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,
@@ -694,6 +755,12 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
/** 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) {
mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
interpolator));
}
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator) {
mIsGoingToHome = Float.compare(end, 1) == 0;
ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration);
anim.setInterpolator(interpolator);
@@ -705,7 +772,26 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
: 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 +843,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
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() {
@@ -840,6 +929,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
return;
}
mQuickScrubController.onFinishedTransitionToQuickScrub();
mRecentsView.setRunningTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
RecentsModel.getInstance(mContext).onOverviewShown(false, TAG);
}
public void onQuickScrubProgress(float progress) {
@@ -903,6 +995,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
if (mLongSwipeController != null) {
mLongSwipeController.destroy();
setTargetAlphaProvider((t, a1) -> a1);
// Rebuild animations
buildAnimationController();
@@ -944,6 +1037,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mLongSwipeController = mActivityControlHelper.getLongSwipeController(
mActivity, mRecentsAnimationWrapper.targetSet);
onLongSwipeDisplacementUpdated();
setTargetAlphaProvider(mLongSwipeController::getTargetAlpha);
}
private void onLongSwipeGestureFinishUi(float velocity, boolean isFling) {
@@ -958,4 +1052,19 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
() -> 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);
}
}
@@ -30,6 +30,7 @@ import android.graphics.RectF;
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.quickstep.views.TaskThumbnailView;
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 @@ public class ClipAnimationHelper {
// 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 class ClipAnimationHelper {
}
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 @@ public class ClipAnimationHelper {
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) {
@@ -28,6 +28,8 @@ import com.android.systemui.shared.system.TransactionCompat;
@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 @@ public interface RemoteAnimationProvider {
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;
}
}
@@ -26,6 +26,7 @@ public class RemoteAnimationTargetSet {
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 @@ public class RemoteAnimationTargetSet {
this.unfilteredApps = apps;
this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
this.targetMode = targetMode;
}
public RemoteAnimationTargetCompat findTask(int taskId) {
@@ -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);
}
}
@@ -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.Point;
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,7 +48,9 @@ import android.text.StaticLayout;
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;
@@ -98,11 +100,24 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +126,12 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
private final float mFastFlingVelocity;
private final RecentsModel mModel;
private final int mTaskTopMargin;
private final ClearAllButton mClearAllButton;
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 +213,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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);
}
};
@@ -225,8 +239,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
// 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 +260,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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,6 +267,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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()
@@ -272,7 +287,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
updateEmptyMessage();
setFocusable(false);
}
public boolean isRtl() {
@@ -314,14 +328,15 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +345,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
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 +375,14 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
}
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);
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 +404,30 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +440,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +523,12 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +554,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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++) {
@@ -664,12 +634,14 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +681,14 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +731,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
/**
* Updates the page UI based on scroll params.
*/
default void onPageScroll(ScrollState scrollState) {};
default void onPageScroll(ScrollState scrollState) {}
}
public static class ScrollState {
@@ -769,6 +741,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
* 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 +769,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
if (shouldLog) {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
onEndListener.logAction, Direction.UP, index,
TaskUtils.getComponentKeyForTask(task.key));
TaskUtils.getLaunchComponentKeyForTask(task.key));
}
}
}
@@ -805,7 +782,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
AnimatorSet anim = new AnimatorSet();
PendingAnimation pendingAnimation = new PendingAnimation(anim);
int count = getChildCount();
int count = getPageCount();
if (count == 0) {
return pendingAnimation;
}
@@ -816,12 +793,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +816,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
// - 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 +854,15 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +879,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +887,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +905,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +932,37 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +993,24 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
}
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 +1026,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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() {
@@ -1076,7 +1064,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mEmptyTextLayout = null;
mLastMeasureSize.set(getWidth(), getHeight());
}
updateClearAllButtonAlpha();
if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
@@ -1134,16 +1121,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +1179,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +1190,14 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +1225,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +1252,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +1263,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
@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 +1274,14 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
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 +1295,4 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
protected boolean isPageOrderFlipped() {
return true;
}
public boolean performTaskAccessibilityActionExtra(int action) {
return false;
}
}
@@ -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);
}
}
@@ -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 android.util.AttributeSet;
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 @@ import com.android.launcher3.views.ScrimView;
*/
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 mShiftRange;
private final float mShelfOffset;
private float mTopOffset;
private float mShelfTop;
private float mShelfTopAtThreshold;
private float mScrimMoveFactor = 0;
private int mShelfColor;
private int mRemainingScreenColor;
@@ -70,13 +78,13 @@ public class ShelfScrimView extends ScrimView {
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 @@ public class ShelfScrimView extends ScrimView {
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 class ShelfScrimView extends ScrimView {
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 @@ public class ShelfScrimView extends ScrimView {
}
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);
}
}
@@ -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.LauncherAnimUtils;
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 @@ public class TaskMenuView extends AbstractFloatingView {
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 @@ public class TaskMenuView extends AbstractFloatingView {
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 @@ public class TaskMenuView extends AbstractFloatingView {
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 @@ public class TaskMenuView extends AbstractFloatingView {
}
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 @@ public class TaskMenuView extends AbstractFloatingView {
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 @@ public class TaskMenuView extends AbstractFloatingView {
}
});
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 @@ public class TaskMenuView extends AbstractFloatingView {
}
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);
}
}
@@ -29,7 +29,6 @@ import android.graphics.Matrix;
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 class TaskThumbnailView extends View {
}
};
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;
@@ -71,7 +71,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
* 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 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
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 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
}
}
if (getRecentsView().performTaskAccessibilityActionExtra(action)) return true;
return super.performAccessibilityAction(action, arguments);
}
+12 -7
View File
@@ -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>
+2 -2
View File
@@ -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" />
+1 -1
View File
@@ -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>
+1
View File
@@ -149,4 +149,5 @@
<integer name="config_recentsMaxThumbnailCacheSize">6</integer>
<integer name="config_recentsMaxIconCacheSize">12</integer>
<item name="workspace_page_container" type="id" />
</resources>
+3
View File
@@ -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>
+10 -2
View File
@@ -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 -->
+20 -5
View File
@@ -44,13 +44,25 @@ public abstract class BaseActivity extends Activity implements UserEventDelegate
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 @@ public abstract class BaseActivity extends Activity implements UserEventDelegate
/**
* 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 @@ public abstract class BaseActivity extends Activity implements UserEventDelegate
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 {
@@ -61,7 +61,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
private OnStartCallback mOnStartCallback;
private int mThemeRes = R.style.LauncherTheme;
private int mThemeRes = R.style.AppTheme;
private DisplayRotationListener mRotationListener;
@@ -91,10 +91,10 @@ public abstract class BaseDraggingActivity extends BaseActivity
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;
}
}
+45 -33
View File
@@ -72,6 +72,7 @@ public class DeviceProfile {
// Drag handle
public final int verticalDragHandleSizePx;
private final int verticalDragHandleOverlapWorkspace;
// Workspace icons
public int iconSizePx;
@@ -101,8 +102,10 @@ public class DeviceProfile {
// 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 +136,17 @@ public class DeviceProfile {
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 +154,8 @@ public class DeviceProfile {
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 =
@@ -162,6 +178,8 @@ public class DeviceProfile {
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 +194,33 @@ public class DeviceProfile {
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 +338,8 @@ public class DeviceProfile {
// Hotseat
if (isVerticalLayout) {
hotseatBarSizePx = iconSizePx;
hotseatBarSizePx = iconSizePx + hotseatBarSidePaddingStartPx
+ hotseatBarSidePaddingEndPx;
}
hotseatCellHeightPx = iconSizePx;
@@ -425,17 +438,16 @@ public class DeviceProfile {
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 +474,11 @@ public class DeviceProfile {
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 {
@@ -99,6 +99,10 @@ public class ExtendedEditText extends EditText {
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 class ExtendedEditText extends EditText {
}
public void dispatchBackKey() {
UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
hideKeyboard();
if (mBackKeyListener != null) {
mBackKeyListener.onBackKey();
}
@@ -135,6 +139,6 @@ public class ExtendedEditText extends EditText {
nextFocus.requestFocus();
}
}
UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
hideKeyboard();
}
}
@@ -261,7 +261,7 @@ public class FastBitmapDrawable extends Drawable {
/**
* 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) {
+2 -2
View File
@@ -157,10 +157,10 @@ public class Hotseat extends FrameLayout implements LogContainerProvider, Insett
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;
+1 -1
View File
@@ -801,7 +801,7 @@ public class IconCache {
}
private static final class IconDB extends SQLiteCacheHelper {
private final static int RELEASE_VERSION = 22;
private final static int RELEASE_VERSION = 23;
private final static String TABLE_NAME = "icons";
private final static String COLUMN_ROWID = "rowid";
+8 -7
View File
@@ -214,8 +214,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// UI and state for the overview panel
private View mOverviewPanel;
private View mOverviewPanelContainer;
@Thunk boolean mWorkspaceLoading = true;
private OnResumeCallback mOnResumeCallback;
@@ -913,7 +911,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
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 +1171,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
return (T) mOverviewPanel;
}
public <T extends View> T getOverviewPanelContainer() {
return (T) mOverviewPanelContainer;
}
public DropTargetBar getDropTargetBar() {
return mDropTargetBar;
}
@@ -2335,6 +2328,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
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 +2378,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
return true;
}
break;
case KeyEvent.KEYCODE_W:
if (isInState(NORMAL)) {
OptionsPopupView.openWidgets(this);
return true;
}
break;
}
}
return super.onKeyShortcut(keyCode, event);
@@ -640,6 +640,7 @@ public class LauncherModel extends BroadcastReceiver
@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);
+3 -1
View File
@@ -242,8 +242,10 @@ public class LauncherState {
* 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);
}
@@ -296,6 +296,24 @@ public class LauncherStateManager {
}
}
/**
* 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.
@@ -347,12 +365,6 @@ public class LauncherStateManager {
}
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
mState = mCurrentStableState;
}
@Override
public void onAnimationSuccess(Animator animator) {
// Run any queued runnables
@@ -370,7 +382,9 @@ public class LauncherStateManager {
}
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 class LauncherStateManager {
}
public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
clearCurrentAnimation();
setCurrentAnimation(controller.getTarget());
mConfig.userControlled = true;
mConfig.playbackController = controller;
@@ -538,6 +553,9 @@ public class LauncherStateManager {
@Override
public void onAnimationEnd(Animator animation) {
if (playbackController != null && playbackController.getTarget() == animation) {
playbackController = null;
}
if (mCurrentAnimation == animation) {
mCurrentAnimation = null;
}
+16 -26
View File
@@ -24,7 +24,6 @@ import android.animation.TimeInterpolator;
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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
}
if (mScroller.isFinished() && pageScrollChanged) {
restoreScrollOnLayout();
setCurrentPage(getNextPage());
}
}
@@ -620,23 +609,26 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
- 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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
}
}
protected int offsetForPageScrolls() {
return 0;
}
public void setPageSpacing(int pageSpacing) {
mPageSpacing = pageSpacing;
requestLayout();
@@ -747,11 +735,13 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
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;
}
}
+27
View File
@@ -48,6 +48,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.config.FeatureFlags;
@@ -272,6 +273,25 @@ public final class Utilities {
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));
}
@@ -462,6 +482,13 @@ public final class Utilities {
return Math.max(lowerBound, Math.min(value, upperBound));
}
/**
* @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.
+7 -1
View File
@@ -547,6 +547,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
// 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 +2387,12 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
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;
@@ -312,6 +312,12 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
}
}
@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 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
});
}
@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;
@@ -5,6 +5,7 @@ import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
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 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
@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 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
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 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
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);
@@ -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.util.AttributeSet;
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 @@ public class FloatingHeaderView extends LinearLayout implements
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);
}
@@ -21,6 +21,8 @@ import android.text.Editable;
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 @@ import java.util.ArrayList;
* 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 @@ public class AllAppsSearchBarController
mInput.addTextChangedListener(this);
mInput.setOnEditorActionListener(this);
mInput.setOnBackKeyListener(this);
mInput.setOnFocusChangeListener(this);
mSearchAlgorithm = searchAlgorithm;
}
@@ -123,6 +127,13 @@ public class AllAppsSearchBarController
return false;
}
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (!hasFocus) {
mInput.hideKeyboard();
}
}
/**
* Resets the search bar state.
*/
@@ -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 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat
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 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat
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 abstract class AnimatorPlaybackController implements ValueAnimator.Animat
}
}
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;
}
@@ -35,6 +35,7 @@ public class AnimatorSetBuilder {
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<>();
@@ -24,6 +24,8 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.view.animation.PathInterpolator;
import com.android.launcher3.Utilities;
/**
* Common interpolators used in Launcher
@@ -110,12 +112,48 @@ public class Interpolators {
}
};
/**
* Interpolates using a particular section of the damped oscillation function.
* The section is selected by scaling and shifting the function.
*/
public static final Interpolator OSCILLATE = new Interpolator() {
// Used to scale the oscillations horizontally
private final float horizontalScale = 1f;
// Used to shift the oscillations horizontally
private final float horizontalShift = 0.5f;
// Used to scale the oscillations vertically
private final float verticalScale = 1f;
// Used to shift the oscillations vertically
private final float verticalShift = 1f;
@Override
public float getInterpolation(float t) {
t = horizontalScale * (t + horizontalShift);
return (float) ((verticalScale * (Math.exp(-t) * Math.cos(2 * Math.PI * t)))
+ verticalShift);
}
};
private static final float FAST_FLING_PX_MS = 10;
public static Interpolator scrollInterpolatorForVelocity(float velocity) {
return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC;
}
public static Interpolator overshootInterpolatorForVelocity(float velocity) {
return overshootInterpolatorForVelocity(velocity, 1f);
}
/**
* 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.
* @param dampFactor An optional factor to reduce the amount of tension (how far we overshoot).
*/
public static Interpolator overshootInterpolatorForVelocity(float velocity, float dampFactor) {
return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f) / dampFactor);
}
/**
* 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 +173,14 @@ public class Interpolators {
return interpolator.getInterpolation((t - lowerBound) / (upperBound - lowerBound));
};
}
/**
* 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);
}
}
@@ -182,7 +182,12 @@ public class LauncherIcons implements AutoCloseable {
* 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 @@ public class LauncherIcons implements AutoCloseable {
* 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 @@ public class LauncherIcons implements AutoCloseable {
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)
@@ -77,7 +77,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
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 class PreloadIconDrawable extends FastBitmapDrawable {
*/
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 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
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;
@@ -18,7 +18,6 @@ package com.android.launcher3.model;
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.compat.UserManagerCompat;
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.ArrayList;
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 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
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 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
}
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 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
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;
@@ -116,6 +116,10 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L
*/
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)) {
@@ -266,11 +266,7 @@ public abstract class ArrowPopup extends AbstractFloatingView {
}
// 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;
@@ -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 @@ public class QsbContainerView extends FrameLayout {
/**
* 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 @@ public class QsbContainerView extends FrameLayout {
@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 @@ public class QsbContainerView extends FrameLayout {
}
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);
@@ -166,32 +168,18 @@ public class QsbContainerView extends FrameLayout {
}
// 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 +216,83 @@ public class QsbContainerView extends FrameLayout {
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);
}
}
@@ -58,13 +58,9 @@ public class QsbWidgetHostView extends AppWidgetHostView {
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 +72,16 @@ public class QsbWidgetHostView extends AppWidgetHostView {
@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;
}
}
@@ -181,7 +181,12 @@ public class DeepShortcutManager {
* 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) {
@@ -96,10 +96,12 @@ public abstract class InternalStateHandler extends Binder {
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 @@ public abstract class InternalStateHandler extends Binder {
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() {
@@ -29,6 +29,7 @@ import android.animation.Animator;
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 @@ public abstract class AbstractStateChangeTouchController
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 @@ public abstract class AbstractStateChangeTouchController
// 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 @@ public abstract class AbstractStateChangeTouchController
}
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 @@ public abstract class AbstractStateChangeTouchController
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 @@ public abstract class AbstractStateChangeTouchController
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 @@ public abstract class AbstractStateChangeTouchController
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 @@ public abstract class AbstractStateChangeTouchController
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 @@ public abstract class AbstractStateChangeTouchController
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 @@ public abstract class AbstractStateChangeTouchController
} 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 @@ public abstract class AbstractStateChangeTouchController
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 @@ public abstract class AbstractStateChangeTouchController
* 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 @@ public abstract class AbstractStateChangeTouchController
}
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 @@ public abstract class AbstractStateChangeTouchController
}
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 @@ public abstract class AbstractStateChangeTouchController
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();
}
}
}
@@ -153,7 +153,10 @@ public class OptionsPopupView extends ArrowPopup
}
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;
+16 -6
View File
@@ -109,6 +109,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener,
protected int mEndFlatColorAlpha;
protected final int mDragHandleSize;
protected float mDragHandleOffset;
private final Rect mDragHandleBounds;
private final RectF mHitRect = new RectF();
@@ -223,8 +224,14 @@ public class ScrimView extends View implements Insettable, OnChangeListener,
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 @@ public class ScrimView extends View implements Insettable, OnChangeListener,
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);
@@ -54,7 +54,7 @@ public class SpringRelativeLayout extends RelativeLayout {
}
};
private final SparseBooleanArray mSpringViews = new SparseBooleanArray();
protected final SparseBooleanArray mSpringViews = new SparseBooleanArray();
private final SpringAnimation mSpring;
private float mDampedScrollShift = 0;
@@ -85,12 +85,24 @@ public class SpringRelativeLayout extends RelativeLayout {
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);
+1 -1
View File
@@ -22,7 +22,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-targe
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
@@ -33,6 +33,7 @@ import android.support.test.uiautomator.Direction;
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 abstract class AbstractLauncherUiTest {
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 @@ public abstract class AbstractLauncherUiTest {
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 @@ public abstract class AbstractLauncherUiTest {
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 @@ public abstract class AbstractLauncherUiTest {
*/
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 @@ public abstract class AbstractLauncherUiTest {
/**
* 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 abstract class AbstractLauncherUiTest {
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 @@ public abstract class AbstractLauncherUiTest {
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);
}
/**
@@ -41,15 +41,15 @@ public class AllAppsAppLaunchTest extends AbstractLauncherUiTest {
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));
}
}
@@ -46,14 +46,15 @@ public class ShortcutsLaunchTest extends AbstractLauncherUiTest {
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 @@ public class ShortcutsLaunchTest extends AbstractLauncherUiTest {
// 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));
}
}
@@ -48,14 +48,15 @@ public class ShortcutsToHomeTest extends AbstractLauncherUiTest {
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 @@ public class ShortcutsToHomeTest extends AbstractLauncherUiTest {
// 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 @@ public class ShortcutsToHomeTest extends AbstractLauncherUiTest {
// (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));
}
}
@@ -68,12 +68,15 @@ public class WorkTabTest extends AbstractLauncherUiTest {
// 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));
*/
}
}
@@ -49,6 +49,7 @@ import com.android.launcher3.widget.WidgetHostViewLoader;
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 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
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 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
LauncherSettings.Favorites.APPWIDGET_ID))));
}
@Test
@Test @Ignore
public void testPendingWidget_notRestored_removed() throws Exception {
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
@@ -45,6 +45,7 @@ import com.android.launcher3.util.rule.ShellCommandRule;
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 @@ public class RequestPinItemTest extends AbstractLauncherUiTest {
}
@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 @@ public class RequestPinItemTest extends AbstractLauncherUiTest {
});
}
@Test
@Test @Ignore
public void testPinWidgetNoConfig_customPreview() throws Throwable {
// Command to set custom preview
Intent command = RequestPinItemActivity.getCommandIntent(
@@ -109,7 +113,7 @@ public class RequestPinItemTest extends AbstractLauncherUiTest {
}, command);
}
@Test
@Test @Ignore
public void testPinWidgetWithConfig() throws Throwable {
runTest("pinWidgetWithConfig", true, new ItemOperator() {
@Override
@@ -122,7 +126,7 @@ public class RequestPinItemTest extends AbstractLauncherUiTest {
});
}
@Test
@Test @Ignore
public void testPinShortcut() throws Throwable {
// Command to set the shortcut id
Intent command = RequestPinItemActivity.getCommandIntent(
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show More